Search code examples
stringlistprologdcg

Prolog getting head and tail of string


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.


Solution

  • 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.