Your choices
Your choice of predicate is as follows:
Two concatenable terms as input (can split)
- atom_concat/3 (ISO)
- string_concat/3
Two more general concatenable terms as input (cannot split because arguments 1 and 2 are too general)
A list of concatenable terms as input (never split)
- atomic_list_concat/2 - generates atom at argument 2. Refuses string at argument 2 in accept mode (that's likely a bug).
- atomics_to_string/2 - generates string at argument 2. Refuses atom at argument 2 in accept mode (that's likely a bug).
A list of concatenable terms as input, and intersperse another string (can split at interspersed string)
- atomic_list_concat/3 - concatenate with separator ("intersperse", "join"). Refuses string at argument 3 in accept mode (that's likely a bug).
- atomics_to_string/3 - concatenate with separator ("intersperse", "join"). Refuses atom at argument 3 in accept mode (that's likely a bug).
Another example
Useful for disassembling a path relative:
?- atomic_list_concat([Seg1,Seg2,Seg3],/,"my/fav/location"). Seg1 = my, Seg2 = fav, Seg3 = location.
Or an absolute path:
?- atomic_list_concat(['',Seg1,Seg2,Seg3],/,"/my/fav/location"). Seg1 = my, Seg2 = fav, Seg3 = location.
Test code
:- begin_tests(atomic_list_concat_3).
test("atomic_list_concat/3 always generates atom at the 3rd argument from any list-of-atomics") :-
atomic_list_concat([a,b],'&',X1A),
assertion(X1A=='a&b'),
atomic_list_concat(["a","b"],'&',X2A),
assertion(X2A=='a&b'),
atomic_list_concat([a,"b",12],'&',X3A),
assertion(X3A=='a&b&12'),
atomic_list_concat([a,b],"&",X1S),
assertion(X1S=='a&b'),
atomic_list_concat(["a","b"],"&",X2S),
assertion(X2S=='a&b'),
atomic_list_concat([a,"b",12],"&",X3S),
assertion(X3S=='a&b&12').
test("atomic_list_concat/3 in 'accept mode'") :-
atomic_list_concat([a,b],'&','a&b'),
atomic_list_concat(["a","b"],'&','a&b'),
atomic_list_concat([a,"b",12],'&','a&b&12'),
atomic_list_concat([a,b],"&",'a&b'),
atomic_list_concat(["a","b"],"&",'a&b'),
atomic_list_concat([a,"b",12],"&",'a&b&12').
test("atomic_list_concat/3 in 'accept mode' (surprisingly) disallows string for argument 2",fail) :-
atomic_list_concat(["a",b],'&',"a&b").
test("atomic_list_concat/3 edge case works as expected") :-
atomic_list_concat([],'',X1),
assertion(X1==''),
atomic_list_concat([''],'',X2),
assertion(X2==''),
atomic_list_concat(['',''],'',X3),
assertion(X3==''),
atomic_list_concat([""],'',X4),
assertion(X4==''),
atomic_list_concat(["",""],'',X5),
assertion(X5=='').
test("atomic_list_concat/3 splits as it should") :-
atomic_list_concat(L1,",",abc),
assertion(L1==[abc]),
atomic_list_concat(L2,'&','a&b&c'),
assertion(L2==[a,b,c]),
atomic_list_concat(L3,'&',"a&b&c"),
assertion(L3==[a,b,c]),
atomic_list_concat(L4,"&","a&b&c"),
assertion(L4==[a,b,c]),
atomic_list_concat(L5,'&','a&&b&&c'),
assertion(L5==[a,'',b,'',c]),
atomic_list_concat(L6,'&','&a&&b&&c&'),
assertion(L6==['',a,'',b,'',c,'']).
test("atomic_list_concat/3 processing input consisting only of splitters") :-
atomic_list_concat(L1,'&','&&&'),
assertion(L1==['','','','']),
atomic_list_concat(L2,'foo','foofoo'),
assertion(L2==['','','']).
:- end_tests(atomic_list_concat_3).
Edge asymmetry
You can assemble with the empty string:
?- atomic_list_concat([fo,o],'',X). X = foo.
But you can't disassemble with the empty string:
?- atomic_list_concat(L,'',foo). ERROR: Domain error: `non_empty_atom' expected, found `'''