Prolog getting head and tail of string
Asked Answered
E

3

8

I'm trying to wrap my brain around Prolog for the first time (SWI-Prolog) and I'm struggling with what I'm sure are the basics. I'm trying to take a string such as "pie" and print out the military NATO spelling of it to look something like this:

spellWord("Pie").
Papa
India
Echo

Currently I'm just trying to verify that I'm using the [H|T] syntax and Write function correctly. My function is:

spellWord(String) :- String = [H|T], writeChar(H), spellWord(T).

writeChar(String) :- H == "P", print4("Papa").

When making a call to spellWord("Pie"). this currently just returns false.

Elegancy answered 13/4, 2016 at 16:45 Comment(0)
B
9

SWI-Prolog has several different representation of what you might call "strings".

  • List of character codes (Unicode);
  • List of chars (one-letter atoms);
  • Strings, which are "atomic" objects, and can be manipulated only with the built-in predicates for strings;
  • And finally, of course, atoms.

You should read the documentation, but for now, you have at least two choices.

Choice 1: Use a flag to make double-quoted strings code lists

$ swipl --traditional
Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.19-57-g9d8aa27)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
Please visit http://www.swi-prolog.org for details.

For help, use ?- help(Topic). or ?- apropos(Word).

?- X = "abc".
X = [97, 98, 99].

At this point, your approach should work, as you now have a list.

Choice 2: Use the new code list syntax with back-ticks

?- X = `abc`.
X = [97, 98, 99].

And, of course, there are predicates that convert between atoms, code lists, char lists, and strings. So, to make a list of chars (one-character atoms), you have:

  • atom_chars/2
  • char_code/2
  • string_chars/2

As for your predicate definition, consider using unification in the head. Also, don't mix side effects (printing) with what the predicate does. Let the top level (the Prolog interpreter) do the printing for you.

nato(p, 'Papa').
nato(i, 'India').
nato(e, 'Echo').
% and so on

word_nato([], []).
word_nato([C|Cs], [N|Ns]) :-
    char_code(Char, C),
    char_type(U, to_lower(Char)),
    nato(U, N),
    word_nato(Cs, Ns).

And with this:

?- word_nato(`Pie`, Nato).
Nato = ['Papa', 'India', 'Echo'].

I used chars (one-letter atoms) instead of character codes because those are easier to write.


And finally, you can use the following flag, and set_prolog_flag/2 at run time to change how Prolog treats a string enclosed in double quotes.

For example:

$ swipl
Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.19-40-g2bcbced)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
Please visit http://www.swi-prolog.org for details.

For help, use ?- help(Topic). or ?- apropos(Word).

?- current_prolog_flag(double_quotes, DQs).
DQs = string.

?- string("foo").
true.

?- set_prolog_flag(double_quotes, codes).
true.

?- X = "foo".
X = [102, 111, 111].

?- set_prolog_flag(double_quotes, chars).
true.

?- X = "foo".
X = [f, o, o].

?- set_prolog_flag(double_quotes, atom).
true.

?- X = "foo".
X = foo.
Biographer answered 13/4, 2016 at 19:30 Comment(4)
How would I make this work to accept input in single quotes, such as 'pie'Elegancy
@Elegancy Just use the appropriate conversion predicate first. I listed them in the answer above.Biographer
There is also the option set_prolog_flag(double_quotes, chars)Borgia
@Borgia Yes, but you have to set it explicitly in the program, not with a command line flag? I thought about it but I thought this is beside the point.... I should add it for completeness.Biographer
B
7

Regardless of the Prolog system you are using and unless you have to maintain existing code, stick to set_prolog_flag(double_quotes, chars). This works in many systems like B, GNU, IF, IV, Minerva, Scryer, SICStus, SWI, Tau, Trealla, YAP. So it is a safe bet. The other options mentioned by @Boris are hard to debug. One is even specific to SWI only.

?- set_prolog_flag(double_quotes, chars).
   true.
?- L = "abc".
   L = [a,b,c].

With library(double_quotes) these strings can be printed more compactly.

In SWI, the best you can do is to put in your .swiplrc the lines:

:- set_prolog_flag(back_quotes, string).
:- set_prolog_flag(double_quotes, chars).
:- use_module(library(double_quotes)).

For your concrete example, it is a good idea to avoid producing side-effects immediately. Instead consider defining a relation between a word and the spelling:

word_spelling(Ws, Ys) :-
   phrase(natospelling(Ws), Ys).

natospelling([]).
natospelling([C|Cs]) -->
   {char_lower(C, L)},
   nato(L),
   "\n",
   natospelling(Cs).

nato(p) --> "Papa".
nato(i) --> "India".
nato(e) --> "Echo".

char_lower(C, L) :-
   char_type(L, to_lower(C)).

?- word_spelling("Pie",Xs).
   Xs = "Papa\nIndia\nEcho\n".
?- word_spelling("Pie",Xs), format("~s",[Xs]).
Papa
India
Echo
Xs = "Papa\nIndia\nEcho\n".

And here is your original definition. Most of the time, however, rather stick with the pure core of it.

spellWord(Ws) :-
   word_spelling(Ws, Xs),
   format("~s", [Xs]).

Also note that SWI's built-in library(pio) only works for codes and leaves unnecessary choice-points open. Instead, use this replacement which works for chars and codes depending on the Prolog flag.

Historically, characters were first represented as atoms of length one. That is, 1972 in Prolog 0. However, there, strings were represented in a left-associative manner which facilitated suffix matching.

plur(nil-c-i-e-l, nil-c-i-e-u-x).

Starting with Prolog I, 1973, double quotes meant a list of characters like today.

In 1977, DECsystem 10 Prolog changed the meaning of double quotes to lists of characters codes and used codes in place of chars. This made some I/O operations a little bit more efficient, but made debugging such programs much more difficult [76,105,107,101,32,116,104,105,115] - can you read it?

ISO Prolog supports both. There is a flag double_quotes that indicates how double quotes are interpreted. Also, character related built-ins are present for both:

char_code/2

atom_chars/2, number_chars/2, get_char/1/2, peek_char/1/2, put_char/1/2

atom_codes/2, number_codes/2, get_code/1/2, peek_code/1/2, put_code/1/2
Borgia answered 15/4, 2016 at 11:18 Comment(0)
G
1

The problems with your code are:

spellWord(String) :- String = [H|T], writeChar(H), spellWord(T).

When you give this predicate a long string, it will invoke itself with the tail of that string. But when String is empty, it cannot be split into [H|T], therefore the predicate fails, returning false.

To fix this, you have to define additionally:

spellWord([]).

This is the short form of:

spellWord(String) :- String = [].

Your other predicate also has a problem:

writeChar(String) :- H == "P", print4("Papa").

You have two variables here, String and H. These variables are in no way related. So no matter what you pass as a parameter, it will not influence the H that you use for comparison. And since the == operator only does a comparison, without unification, writeChar fails at this point, returning false. This is the reason why there is no output at all.

Girt answered 21/4, 2016 at 18:31 Comment(2)
There is still the problem with what you call a string and whether it is a list. If you just install the latest SWI-Prolog, and type in a string between double quotes, as in "foo", this is most definitely not a list, but an atomic object.Biographer
PS. Even thought the predicate spellWord/1 will fail, it should print out each element of the list. So, in a way it does what it was intended to do.Biographer

© 2022 - 2024 — McMap. All rights reserved.