without any regard to efficiency, this seems fairly similar
atomic_list_concat_(L, Sep, Atom) :-
( atom(Sep), ground(L), is_list(L) )
-> list_atom(L, Sep, Atom)
; ( atom(Sep), atom(Atom) )
-> atom_list(Atom, Sep, L)
; instantiation_error(atomic_list_concat_(L, Sep, Atom))
.
list_atom([Word], _Sep, Word).
list_atom([Word|L], Sep, Atom) :-
list_atom(L, Sep, Right),
atom_concat(Sep, Right, Right1),
atom_concat(Word, Right1, Atom).
atom_list(Atom, Sep, [Word|L]) :-
sub_atom(Atom, X,N,_, Sep),
sub_atom(Atom, 0,X,_, Word),
Z is X+N,
sub_atom(Atom, Z,_,0, Rest),
!, atom_list(Rest, Sep, L).
atom_list(Atom, _Sep, [Atom]).
rationale
atomic_list_concat/3 is a convenience built-in in SWI-Prolog, I think my code doesn't cover fully the specification (mainly because I lack SICStus Prolog for testing). A noteworthy difference is the qualification of acceptable types. I'm sticking to atom instead of atomic types for list' elements, because atom_concat/3 it's an ISO built-in, as such should ban some patterns accepted by SWI-Prolog implementation.
About the code, when the type testing has decided about the direction of conversion, the implementation is simple: I opted for a simple recursive concatenation in list_atom/3, that could be easily optimized, for instance adding an accumulator and thus making it tail recursion optimized, while atom_list/3 scan left to right looking for separator, and when it find it, store in list head and get the right part for recursion.
About a simple minded DCG implementation, I used this code to test a nice abstraction list//3 implemented by dcg_util, leading to this compact code:
list_atom(L, Sep, Atom) :-
phrase(list(atom_codes, atom_codes(Sep), L), AtomCs),
atom_codes(Atom, AtomCs).
atom_codes(A) --> {atom_codes(A, Cs)}, Cs.
atom_list(Atom, Sep, L) :-
atom_codes(Atom, AtomCs),
phrase(list(any, atom_codes(Sep), LCs), AtomCs),
!, maplist(atom_codes, L, LCs).
any([]) --> [].
any([C|Cs]) --> [C], any(Cs).
Would be simple enough to directly implement without list//3...