4
votes

append/3 is a very powerful predicate. Suppose I want a predicate that works the same way but for SWI-Prolog's strings.

The easiest approach I see is to transform those strings into lists with string_codes/2, then apply append/3, then use string_codes/2 back. The big problem with this approach is that string_codes/2 does not work if both variables are not unified.

Here is an extremely ugly solution I came up with, which checks which strings are unified to apply string_codes/2 when needed:

append_strings(S1, S2, S3) :-
    nonvar(S1),
    nonvar(S2),!,
    string_codes(S1, A),
    string_codes(S2, B),
    append(A,B,C),
    string_codes(S3, C).

append_strings(S1, S2, S3) :-
    nonvar(S1),
    nonvar(S3),!,
    string_codes(S1, A),
    string_codes(S3, C),
    append(A,B,C),
    string_codes(S2, B).

append_strings(S1, S2, S3) :-
    nonvar(S2),
    nonvar(S3),!,
    string_codes(S2, B),
    string_codes(S3, C),
    append(A,B,C),
    string_codes(S1, A).

append_strings(S1, S2, S3) :-
    nonvar(S3),
    string_codes(S3, C),
    append(A,B,C),
    string_codes(S1, A),
    string_codes(S2, B).

This yields the correct results for the following cases:

?- append_strings("test","auie","testauie").
true.

?- append_strings("test",A,"testauie").
A = "auie".

?- append_strings(A,"auie","testauie").
A = "test" ;
false.

?- append_strings(A,B,"testauie").
A = "",
B = "testauie" ;
A = "t",
B = "estauie" ;
A = "te",
B = "stauie" ;
A = "tes",
B = "tauie" ;
A = "test",
B = "auie" ;
A = "testa",
B = "uie" ;
A = "testau",
B = "ie" ;
A = "testaui",
B = "e" ;
A = "testauie",
B = "" ;
false.

Is there really no way to make things simpler than this? Suppose I want to make a whole bunch of predicates that work with strings just like they would with lists: I obviously don't want to have to write what I did for append/3 for all of them. But I also don't want to work with code strings because then I have no way of knowing whether I am manipulating a normal list or really a string.

4
The easiest way is to not use strings directly. Instead use chars.false
Your definition fails for append_strings(Xs,Ys,Zs). It should rather produce an instantiation error.false
@false But then I can't differentiate between a list and a string. Suppose I want a predicate transpose which transposes a list, but for a string it transposes according to the linebreaks. With a list of chars I have no way of knowing whether I am working with a string or not.Fatalize
Why do you need to differentiate that case at all? If you transpose according to linebreaks, you should define a different relation with a different name anyway.false
Note that almost all Prologs do not have an extra visible string data type. SWI is rather a (recent) exception.false

4 Answers

4
votes

Since the predicate is working on lists, it seems tempting to me to use DCGs. First let's observe that strings in Prolog are really lists of character codes:

   ?- X="test".
X = [116,101,115,116]

Of course this is not very readable, so let's see the characters themselves intead of their codes:

   ?- set_prolog_flag(double_quotes,chars).
yes
   ?- X="test".
X = [t,e,s,t]

That's better. Thinking about the relation the predicate should describe, I opt for a descriptive name like list_list_appended/3. This predicate has one goal: a dcg-rule, let's call it list_list//2, that uses another dcg, let's call it list//2, to actually write the lists:

list_list_appended(L1,L2,L3) :-
    phrase(list_list(L1,L2),L3).   % L3 is L1+L2

list([]) -->                       % if the list is empty ...
    [].                            % ... there's nothing in the list
list([X|Xs]) -->                   % if there's a head element ...
    [X],                           % ... it's in the list
    list(Xs).                      % the tail is also a list

list_list(L1,L2) -->               % the list consists of ...
    list(L1),                      % ... L1 followed by ...
    list(L2).                      % L2

Your example queries:

   ?- list_list_appended("test","auie","testauie").
yes
   ?- list_list_appended(L1,"auie","testauie").
L1 = [t,e,s,t] ? ;
no
   ?- list_list_appended("test",L2,"testauie").
L2 = [a,u,i,e] ? ;
no
   ?- list_list_appended("test","auie",L3).
L3 = [t,e,s,t,a,u,i,e]
   ?- list_list_appended(L1,L2,"testauie").
L1 = [],
L2 = [t,e,s,t,a,u,i,e] ? ;
L1 = [t],
L2 = [e,s,t,a,u,i,e] ? ;
L1 = [t,e],
L2 = [s,t,a,u,i,e] ? ;
L1 = [t,e,s],
L2 = [t,a,u,i,e] ? ;
L1 = [t,e,s,t],
L2 = [a,u,i,e] ? ;
L1 = [t,e,s,t,a],
L2 = [u,i,e] ? ;
L1 = [t,e,s,t,a,u],
L2 = [i,e] ? ;
L1 = [t,e,s,t,a,u,i],
L2 = [e] ? ;
L1 = [t,e,s,t,a,u,i,e],
L2 = [] ? ;
no

As a SWI user you could also use this library in combination with set_prolog_flag(double_quotes,chars). to get the output in desired form. Refer to this answer for details.

3
votes

Just use string_concat/3. Like ISO atom_concat/3, it can be used in many modes, including (-,-,+).

1
votes

This is a more compact definition:

append_strings(S1, S2, S3):-
  append_strings1(S1, L1, [1]-[], N1),
  append_strings1(S2, L2, [1|N1]-N1, N2),
  append_strings1(S3, L3, [1,1|N2]-N2, N3),
  (N3\=[_,_|_] ->instantiation_error(append_strings/3); true),
  append(L1, L2, L3),
  (ground(S1)->true;string_codes(S1, L1)),
  (ground(S2)->true;string_codes(S2, L2)),
  (ground(S3)->true;string_codes(S3, L3)).

append_strings1(S, L, G-NG, N):-
  (ground(S) -> (string_codes(S, L), N=G) ; N=NG).

It checks whether each argument is ground and tries to convert to codes, then checks if either the third argument is ground or the other two are, and throws an instantiation error if conditions are not met.

After the append it converts back to string arguments which where not ground.

1
votes

there has been a similar question some time ago, I will show my proposal, revised

:- meta_predicate when_(0).
when_(P) :-
    strip_module(P,_,Q), Q =.. [_|As],
    or_list(As, Exp), % hurry debugging :-) display(Exp),
    when(Exp, P).

or_list([A], ground(A)) :- !.
or_list([A|As], (ground(A);Exp)) :- or_list(As, Exp).

append_strings(S1, S2, S3) :-
    maplist(when_, [string_codes(S1, A), string_codes(S2, B), append(A,B,C), string_codes(S3, C)]).

If you are interested, I can add an operator to hide the syntax details, to get something like

append_strings(S1, S2, S3) -:-
    string_codes(S1, A), string_codes(S2, B), append(A,B,C), string_codes(S3, C).