ChatGPT prompts - SWI-Prolog (1)

Installation

Prompt

How to install SWI-Prolog on Windows

ChatGPT Completion

To install SWI-Prolog on Windows, follow these steps:

  1. Download the SWI-Prolog installation file for Windows from the official website (SWI-Prolog downloads)
  2. Double-click on the installation file to start the setup process.
  3. Follow the on-screen instructions to complete the installation.
  4. Add the SWI-Prolog bin folder to the PATH environment variable to make it accessible from the command line.
  5. Example: If you installed SWI-Prolog in C:\Program Files\swipl, add C:\Program Files\swipl\bin to your PATH environment variable.

You can now start the SWI-Prolog interpreter by opening the Command Prompt and typing swipl.

If you search for how to install SWI-Prolog on Windows you may find some YouTube videos, using winget and other sets of instructions. The official place to find the SWI-Prolog download for the installer on Windows is correctly given by ChatGPT, SWI-Prolog downloads.

For more detailed instructions with screen shots of steps see:
Install SWI-Prolog development version on Windows 10

Constraints - Finite Domain

Prompt

Create SWI-Prolog Prolog code to solve the 8 queens problem using finite domain constraints.

ChatGPT Completion

Here is an example SWI-Prolog code that solves the 8 queens problem using finite domain constraints:

:- use_module(library(clpfd)).

% This predicate finds a solution to the 8 queens problem and prints it.
queens :-
    % Define the domain of the variables.
    length(Queens, 8),
    Queens ins 1..8,
    % Define the constraints.
    all_different(Queens),
    diagonal_constraints(Queens),
    % Use labeling to find a solution.
    labeling([], Queens),
    % Print the solution.
    print_solution(Queens).

% This predicate defines the diagonal constraints.
diagonal_constraints([]).
diagonal_constraints([Q|Queens]) :-
    diagonal_constraints(Queens, Q, 1),
    diagonal_constraints(Queens).

diagonal_constraints([], _, _).
diagonal_constraints([Q|Queens], Q0, D0) :-
    D is abs(Q0 - Q),
    D \= D0,
    D1 is D0 + 1,
    diagonal_constraints(Queens, Q0, D1).

% This predicate prints a solution to the 8 queens problem.
print_solution([]).
print_solution([Q|Queens]) :-
    print_row(Q),
    print_solution(Queens).

print_row(Q) :-
    Length is 8 - Q,
    write('|'),
    print_spaces(Length),
    write('Q'),
    print_spaces(Q - 1),
    write('|'),
    nl.

print_spaces(0).
print_spaces(N) :-
    write(' '),
    N1 is N - 1,
    print_spaces(N1).

To use this code, simply load it into SWI-Prolog and call the queens predicate:

?- queens.
|       Q|
|Q      8 |
|  Q    6 |
|    Q  1 |
|     Q 5 |
|  Q     3|
|      Q 2|
|       Q7|
true .

This will find a solution to the 8 queens problem and print it. The solution will consist of a list of 8 integers, each representing the row of the queen in the corresponding column. The output is formatted to display the board with the queen positions using ASCII characters.

The code does not result in any warnings or errors with make/0 on SWI-Prolog, it does result in an error when run.

?- queens.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [13] _27268 is abs(_27278-_27280)

For the more general problem of queens on a square board see:
clpfd - Understanding CLP(FD) Prolog code of N-queens problem - Stack Overflow

Data conversion to/from JSON

In programming, you often need to convert data to or from other formats for use with other software. One of the most common formats for interchanging data is JSON. One of the easiest ways to get ChatGPT to work with a table of data is to have it generate a small set of data and instruct it to limit the size, often by specifying a number.

Prompt

Generate data about the first 10 U.S. presidents as Prolog facts.

% Facts about the first 10 U.S. Presidents
president(1, george_washington, 1789, 1797).
president(2, john_adams, 1797, 1801).
president(3, thomas_jefferson, 1801, 1809).
president(4, james_madison, 1809, 1817).
president(5, james_monroe, 1817, 1825).
president(6, john_q_adams, 1825, 1829).
president(7, andrew_jackson, 1829, 1837).
president(8, martin_van_buren, 1837, 1841).
president(9, william_henry_harrison, 1841).
president(10, john_tyler, 1841, 1845).

Prompt

For SWI-Prolog create a Prolog predicate to convert president data to JSON. Use SWI-Prolog libraries as needed.

ChatGPT Completion

Here is a Prolog predicate that converts the Prolog facts about the first 10 U.S. presidents to JSON using the SWI-Prolog libraries:

:- use_module(library(http/json)).

presidents_json(PresidentsJson) :-
    findall(President, president(President), Presidents),
    maplist(president_json, Presidents, PresidentsJsonList),
    dict_create(PresidentsDict, presidents, PresidentsJsonList),
    json_write_dict(current_output, PresidentsDict, [indent(2)]).

president_json(president(Number, Name, StartYear, EndYear), PresidentJson) :-
    dict_create(PresidentDict, president, [
        number-Number,
        name-Name,
        start_year-StartYear,
        end_year-EndYear
    ]),
    PresidentJson = PresidentDict.

The presidents_json/1 predicate takes one argument, PresidentsJson, which is the JSON representation of the first 10 U.S. presidents. The predicate uses the findall/3 built-in predicate to gather a list of all the president facts, then maps over that list using the president_json/2 predicate to convert each fact to a JSON object. Finally, the dict_create/3 predicate is used to create a dictionary with the list of JSON objects, which is then written to the current output as formatted JSON using the json_write_dict/3 predicate.

The president_json/2 predicate takes a president/4 fact and converts it to a JSON object. It uses the dict_create/3 predicate to create a dictionary with the relevant fields for the president, then creates a new dictionary with the president key and the dictionary of fields. This new dictionary is then unified with the PresidentJson argument.

The code results in a warning with make/0 on SWI-Prolog.

Warning:    Singleton variables: [PresidentsJson]

It results in an error when run.

?- presidents_json(_).
ERROR: Type error: `key-value' expected, found `president{end_year:1797,name:george_washington,number:1,start_year:1789}' (a dict)

Here is correct code.

presidents_json :-
  findall(president(Number, Name, StartYear, EndYear), president(Number, Name, StartYear, EndYear), Presidents),
  maplist(president_json, Presidents, PresidentsJsonList),
  dict_create(PresidentsDict, presidents, PresidentsJsonList),
  json_write_dict(current_output, PresidentsDict, [indent(2)]).

president_json(president(Number, Name, StartYear, EndYear), Name-PresidentDict) :-
  dict_create(PresidentDict, president, [
      number-Number,
      name-Name,
      start_year-StartYear,
      end_year-EndYear
  ]).

The first change is make the Goal and Template arguments for finall/3 the same,

findall(president(Number, Name, StartYear, EndYear), president(Number, Name, StartYear, EndYear), Presidents)

The second change is to change the result of president_json/2 to return a key-value instead of a dict.

The third change is to just remove the argument for presidents_json/1 as it is not used.

Example run of corrected code on SWI-Prolog.

?- example:presidents_json.
{
    "andrew_jackson": {
      "end_year":1837,
      "name":"andrew_jackson",
      "number":7,
      "start_year":1829
    },
    "george_washington": {
      "end_year":1797,
      "name":"george_washington",
      "number":1,
      "start_year":1789
    },
    "james_madison": {
      "end_year":1817,
      "name":"james_madison",
      "number":4,
      "start_year":1809
    },
    "james_monroe": {
      "end_year":1825,
      "name":"james_monroe",
      "number":5,
      "start_year":1817
    },
    "john_adams": {
      "end_year":1801,
      "name":"john_adams",
      "number":2,
      "start_year":1797
    },
    "john_quincy_adams": {
      "end_year":1829,
      "name":"john_quincy_adams",
      "number":6,
      "start_year":1825
    },
    "john_tyler": {
      "end_year":1845,
      "name":"john_tyler",
      "number":10,
      "start_year":1841
    },
    "martin_van_buren": {
      "end_year":1841,
      "name":"martin_van_buren",
      "number":8,
      "start_year":1837
    },
    "thomas_jefferson": {
      "end_year":1809,
      "name":"thomas_jefferson",
      "number":3,
      "start_year":1801
    },
    "william_henry_harrison": {
      "end_year":1841,
      "name":"william_henry_harrison",
      "number":9,
      "start_year":1841
    }
  }
true.

This verifies as valid JSON at https://jsonlint.com/

Prompt

For SWI-Prolog create a Prolog predicate to convert president data as JSON to Prolog facts. Use SWI-Prolog libraries as needed.

ChatGPT Completion

Here is a Prolog predicate that converts the JSON data to Prolog facts using the SWI-Prolog libraries:

:- use_module(library(http/json)).

presidents_from_json(Presidents) :-
    json_file('presidents.json', JsonData),
    dict_pairs(JsonData, presidents, Pairs),
    maplist(president_from_json, Pairs, Presidents).

president_from_json(Name-PresDict, president(Number, Name, StartYear, EndYear)) :-
    dict_pairs(PresDict, _, [
        number-Number,
        start_year-StartYear,
        end_year-EndYear
    ]).

The presidents_from_json/1 predicate reads the JSON data from a file using the json_file/2 predicate, then converts the dictionary of presidents into a list of key-value pairs using the dict_pairs/3 predicate. It then maps over the pairs using the president_from_json/2 predicate to convert each key-value pair into a president/4 fact.

The president_from_json/2 predicate takes a key-value pair from the JSON data and converts it into a president/4 fact. It first extracts the name of the president from the key, then extracts the relevant fields from the value dictionary using the dict_pairs/3 predicate. Finally, it unifies the extracted fields with a new president/4 fact.

The ChatGPT code has design flaws.

Here is similar working code.

json(Json) :-
    Json =
'{
    "andrew_jackson": {
      "end_year":1837,
      "name":"andrew_jackson",
      "number":7,
      "start_year":1829
    },
    "george_washington": {
      "end_year":1797,
      "name":"george_washington",
      "number":1,
      "start_year":1789
    },
    "james_madison": {
      "end_year":1817,
      "name":"james_madison",
      "number":4,
      "start_year":1809
    },
    "james_monroe": {
      "end_year":1825,
      "name":"james_monroe",
      "number":5,
      "start_year":1817
    },
    "john_adams": {
      "end_year":1801,
      "name":"john_adams",
      "number":2,
      "start_year":1797
    },
    "john_quincy_adams": {
      "end_year":1829,
      "name":"john_quincy_adams",
      "number":6,
      "start_year":1825
    },
    "john_tyler": {
      "end_year":1845,
      "name":"john_tyler",
      "number":10,
      "start_year":1841
    },
    "martin_van_buren": {
      "end_year":1841,
      "name":"martin_van_buren",
      "number":8,
      "start_year":1837
    },
    "thomas_jefferson": {
      "end_year":1809,
      "name":"thomas_jefferson",
      "number":3,
      "start_year":1801
    },
    "william_henry_harrison": {
      "end_year":1841,
      "name":"william_henry_harrison",
      "number":9,
      "start_year":1841
    }
  }'.

:- dynamic president/4.

presidents_from_json :-
    json(Json_string),
    open_string(Json_string,Stream),
    json_read_dict(Stream, Presidents_dict),
    dict_pairs(Presidents_dict,_,Pairs),
    pairs_values(Pairs,Values),
    dicts_to_compounds(Values, [number,name,start_year,end_year], dict_fill(null), Compounds),
    maplist(assert_new_functor(president),Compounds).

assert_new_functor(Functor,Compound0) :-
    compound_name_arguments(Compound0,_,Arguments),
    compound_name_arguments(Compound,Functor,Arguments),
    (
        Compound, !
    ;
        assertz(Compound)
    ).

Example run using SWI-Prolog

?- presidents_from_json.
true.

?- listing(M:president/4).
:- dynamic president/4.

president(7, "andrew_jackson", 1829, 1837).
president(1, "george_washington", 1789, 1797).
president(4, "james_madison", 1809, 1817).
president(5, "james_monroe", 1817, 1825).
president(2, "john_adams", 1797, 1801).
president(6, "john_quincy_adams", 1825, 1829).
president(10, "john_tyler", 1841, 1845).
president(8, "martin_van_buren", 1837, 1841).
president(3, "thomas_jefferson", 1801, 1809).
president(9, "william_henry_harrison", 1841, 1841).

Guess a number

Can ChatGPT generate code that prompts a user for input.

Prompt

In Prolog create a program to allow a user to guess a number by prompting for a number and then replying higher or lower and repeating until the number is found.

ChatGPT Completion

%% Main query to generate guesses
guess(N):-
    write('guess a number: '),
    read(Guess),    
    (
        Guess == N
        ->  write('You guessed correctly!');
        (
            Guess > N
            ->  write('Lower!'),
            nl,
            guess(N);
            write('Higher!'),
            nl,
            guess(N)
        )
    ).

Prompt

Demonstrate this code.

ChatGPT Completion

?- guess(7).
guess a number: 5
Higher!
guess a number: 9
Lower!
guess a number: 7
You guessed correctly!

Prompt

Explain the code.

ChatGPT Completion

This code allows a user to guess a number by prompting for a number and then replying higher or lower and repeating until the number is found. The code starts by setting up the main query, guess(N), which prompts the user to guess a number and reads the user’s input. If the user’s input is equal to N (the number to guess), then the user is told that they guessed correctly. If the user’s input is greater than N, then the user is told to guess lower and the query is repeated. If the user’s input is less than N, then the user is told to guess higher and the query is repeated. The query repeats until the user guesses correctly.

Example run using SWI-Prolog

?- guess(23).
guess a number: 10.
Higher!
guess a number: |: 20.
Higher!
guess a number: |: 30.
Lower!
guess a number: |: 24.
Lower!
guess a number: |: 22.
Higher!
guess a number: |: 23.
You guessed correctly!
true.

Notice that each input number ends with a period. This is because the Prolog predicate read/1 expects a Prolog term and all Prolog terms end with a period. A better solution would use read_string/5. See if you can use Prompt Engineering to get ChatGPT to create code for the guessing game using read_string/5.

Prolog modules

Prompt

In Prolog create two files each using use_module. One module is a list of 5 Greek gods and their father. The other module is an ancestor predicate. Demonstrate and explain the code.

ChatGPT Completion

Module 1: gods.pl

% gods.pl
% This module contains a list of five Greek gods and their father

:- module(gods, [god/2]).

god(zeus, kronos).
god(hera, kronos).
god(poseidon, kronos).
god(hades, kronos).
god(ares, zeus).

Module 2: ancestor.pl

% ancestor.pl
% This module contains an ancestor predicate

:- module(ancestor, [ancestor/2]).

:- use_module(gods).

ancestor(X, Y) :-
    god(X, Y).
ancestor(X, Y) :-
    god(X, Z),
    ancestor(Z, Y).

The first module, gods.pl, defines a module gods that contains a predicate god/2 that takes two arguments, X and Y, representing a Greek god and its father. The module contains facts that define the five gods given and their father.

The second module, ancestor.pl, defines a module ancestor that contains a single predicate ancestor

For some reason it did not demonstrate the code so asked separately.

Prompt

Demonstrate the code.

ChatGPT Completion

?- use_module(gods).
true.

?- use_module(ancestor).
true.

?- god(X, kronos).
X = zeus ;
X = hera ;
X = poseidon ;
X = hades ;
false.

?- ancestor(X, kronos).
X = zeus ;
X = hera ;
X = poseidon ;
X = hades ;
X = ares ;
false.

Tabling

Prompt

Create a Prolog predicate to generate Fibonacci numbers using tabling.

ChatGPT Completion

Here’s an implementation of a Prolog predicate to generate the nth Fibonacci number using tabling:

:- table fib/2.

fib(0, 0).
fib(1, 1).
fib(N, F) :-
   N > 1,
   N1 is N - 1,
   N2 is N - 2,
   fib(N1, F1),
   fib(N2, F2),
   F is F1 + F2.

In this implementation, the :- table fib/2. directive at the beginning of the code tells Prolog to use tabling for the fib/2 predicate. Tabling is a mechanism for memoization in Prolog that caches the results of predicate calls to avoid redundant computations.

The rest of the implementation is similar to the previous implementation, defining two base cases fib(0, 0) and fib(1, 1), and a recursive case fib(N, F) to calculate the Nth Fibonacci number. The recursive case uses the Prolog built-in is/2 predicate to evaluate expressions and unify their values with variables. The predicate calculates the (N-1)th and (N-2)th Fibonacci numbers first and then adds them to get the Nth Fibonacci number.

With tabling, the results of each calculation are stored in a table, so if the same calculation is performed again in the future, the result can be retrieved from the table instead of re-computing it. This greatly improves the efficiency of the fib/2 predicate, especially for larger values of N.

Example run using SWI-Prolog

?- maplist(fib,[0,1,2,3,4,5,6,7,8,9,10],F).
F = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55].

Collections

Prompt

Create a Prolog facts for eight Greek gods. For each god include three properties.

ChatGPT Completion

god(zeus, king, lightning).
god(hera, queen, marriage).
god(poseidon, sea, earthquakes).
god(demeter, harvest, agriculture).
god(ares, war, courage).
god(aphrodite, love, beauty).
god(athena, wisdom, war).
god(apollo, music, archery).
god(artemis, hunt, wild_animals).

In the above example, each fact is written as “god(Name, Property1, Property2)” where Name is the name of the god, Property1 and Property2 are two of the three properties associated with that god.

findall

Prompt

For the Prolog god facts use the findall predicated to collect the name.

ChatGPT Completion

Here is an example of using the findall/3 predicate to collect the names of the gods:

all_gods(Gods) :-
    findall(Name, god(Name, _, _), Gods).

In the above code, the findall/3 predicate collects all the names of the gods into a list Gods . The first argument to findall/3 is the template for the elements to be collected, in this case Name . The second argument is a goal that defines the elements to be collected, in this case god(Name, _, _) . The third argument is the list in which the collected elements are stored, in this case Gods . The predicate all_gods/1 can be used to obtain the list of all the gods, for example:

?- all_gods(Gods).
Gods = [zeus, hera, poseidon, demeter, ares, aphrodite, athena, apollo, artemis].

setof

Prompt

For the Prolog god facts use the setof predicated to collect the name.

ChatGPT Completion

Here is an example of using the setof/3 predicate to collect the names of the gods:

all_gods(Gods) :-
    setof(Name, god(Name, _, _), Gods).

In the above code, the setof/3 predicate collects all the names of the gods into a list Gods . The first argument to setof/3 is the template for the elements to be collected, in this case Name . The second argument is a goal that defines the elements to be collected, in this case god(Name, _, _) . The third argument is the list in which the collected elements are stored, in this case Gods . The predicate all_gods/1 can be used to obtain the list of all the gods, for example:

?- all_gods(Gods).
Gods = [apollo, ares, artemis, aphrodite, athena, demeter, hera, poseidon, zeus].

Note that setof/3 returns the collected elements in sorted order, so the list of gods returned by the above code will be sorted alphabetically.

setof/3 is typically used to select all facts sorted. Because setof/3 is used incorrectly the result is different.

Example run using SWI-Prolog

?- all_gods(Gods).
Gods = [demeter] ;
Gods = [artemis] ;
Gods = [zeus] ;
Gods = [aphrodite] ;
Gods = [apollo] ;
Gods = [hera] ;
Gods = [poseidon] ;
Gods = [ares] ;
Gods = [athena]

Here is the corrected code.

all_gods(Gods) :-
    setof(Name, [A,B]^god(Name, A, B), Gods).

For details on how to use ^/2 see this reply.

Example run with SWI-Prolog

?- all_gods(Gods).
Gods = [aphrodite, apollo, ares, artemis, athena, demeter, hera, poseidon, zeus].

maplist

Prompt

Create a Prolog maplist predicate that extracts the first two parameters of a functor and returns them as the first two arguments of a functor named edge.

ChatGPT Completion

Here’s a Prolog maplist predicate that extracts the first two parameters of a functor and returns them as the first two arguments of a functor named edge:

maplist_edge(List,Result) :-
    maplist(extract_edge, List, Result).

extract_edge(Func, edge(First, Second)) :-
    Func =.. [_|Args],
    [First,Second|_] = Args.

The maplist_edge/2 predicate uses the maplist/2 predicate to apply the extract_edge/2 predicate to each element of the list List. The extract_edge/2 predicate uses =.. (univ operator) to extract the arguments of the input functor Func and then uses pattern matching to extract the first two elements of the argument list as First and Second.

Predicate signature

Prompt

Show a Prolog predicate with a comment containing the predicate signature.

ChatGPT Completion

Here’s an example Prolog predicate for finding the sum of two numbers:

% sum(+A:integer, +B:integer, -C:integer) is det.
sum(A, B, C) :-
   C is A + B.

The predicate signature is as follows:

sum(+A:integer, +B:integer, -C:integer) is det.

This means that sum/3 is a deterministic predicate that takes two integers A and B as input (indicated by the + sign) and returns the sum C of the two numbers as output (indicated by the - sign).

Note: I asked the question the first time and it did not give a correct answer so used thumbs down giving a correct example of a predicate signature. The above is the second time I asked. It also used a different predicate than the first, was really nice to see that it is learning quickly.

Also the SWI-Prolog documentation for sum/3 is

sum(+Vars, +Rel, ?Expr)

notice that the last mode is not - but ?.

HTTP server - Hello World

Prompt

Create a SWI-Prolog predicate to serve a Hello World page. Use initialization/1.

ChatGPT Completion

Here is an example of a SWI-Prolog predicate to serve a “Hello World” page using the http_handler/3 predicate and the initialization/1 directive:

:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).

:- http_handler(root(hello), say_hi, []).

say_hi(Request) :-
    format('Content-type: text/plain~n~n'),
    format('Hello World!~n').

:- initialization(start_server).

start_server :-
    http_server(http_dispatch, [port(8080)]).

The http_handler/3 predicate sets up a handler for the URL path /hello, which is handled by the say_hi/1 predicate. The say_hi/1 predicate outputs the text “Hello World!” in plain text format. The initialization/1 directive calls the start_server/0 predicate when the program is loaded, which starts the HTTP server on port 8080 using http_server/2.

Prompt

How would I start the server and view the page?

ChatGPT Completion

You can start the server by running the following command in the SWI-Prolog console:

?- [file_name].

where file_name is the name of the file containing the above code. The server should then start and be ready to receive requests.

To view the “Hello World” page, you can open a web browser and go to the following URL:

http://localhost:8080/hello

This will send a request to the server running on localhost on port 8080 for the /hello path, which will be handled by the say_hi/1 predicate and return the “Hello World!” message.

HTTP Server - Web Socket Echo

Prompt

Show code that demonstrates echo using web sockets, do the server using SWI-Prolog in a file named server.pl and the client using HTML in a file named index.html the the JavaScript in a file named echo.js.

ChatGPT Completion

Here is an example code for implementing a simple echo server using web sockets in SWI-Prolog and a client in HTML and JavaScript:

server.pl:

:- use_module(library(http/websocket)).
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).

:- http_handler(root(.), http_upgrade_to_websocket(echo), []).
:- http_handler(root(echo), echo, []).

server(Port) :-
    http_server(http_dispatch, [port(Port)]).

echo(WebSocket) :-
    ws_receive(WebSocket, Message),
    (   Message.opcode == close
    ->  true
    ;   ws_send(WebSocket, Message.data, Message.opcode),
        echo(WebSocket)
    ).

index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Echo Client</title>
</head>
<body>
    <input type="text" id="input">
    <button onclick="send()">Send</button>
    <br>
    <textarea id="output" cols="50" rows="10"></textarea>

    <script src="echo.js"></script>
</body>
</html>

echo.js:

var ws = new WebSocket("ws://localhost:8080/echo");

ws.onopen = function(event) {
    console.log("WebSocket connection opened.");
};

ws.onmessage = function(event) {
    document.getElementById("output").value += event.data + "\n";
};

ws.onclose = function(event) {
    console.log("WebSocket connection closed.");
};

function send() {
    var input = document.getElementById("input").value;
    ws.send(input);
    document.getElementById("input").value = "";
}

This code sets up a simple echo server that listens for WebSocket connections on port 8080. The server simply echoes back any messages it receives from a client. The client is an HTML page that includes a text input and a button to send messages to the server. The JavaScript code creates a WebSocket connection to the server and sends any input from the user to the server. When the client receives a message from the server, it appends the message to a text area on the page.

This is close but needs just a few changes to make it work.

server.pl

:- use_module(library(http/websocket)).
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).

:- initialization
    http_server(http_dispatch, [port(8000)]).

:- http_handler(root(.),             http_reply_file('index.html',  []), []).
:- http_handler(root('echo.js'),     http_reply_file('echo.js',     []), []).
:- http_handler(
        root(echo),
        http_upgrade_to_websocket(echo, []),
        [spawn([])]
    ).

echo(WebSocket) :-
    ws_receive(WebSocket, Message, [format(json)]),
    (
        Message.opcode == close
    ->
        true
    ;
        ws_send(WebSocket, Message),
        echo(WebSocket)
    ).

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Echo Client</title>
</head>
<body>
    <input type="text" id="input">
    <button onclick="send()">Send</button>
    <br>
    <textarea id="output" cols="50" rows="10"></textarea>

    <script src="echo.js"></script>
</body>
</html>

echo.js

var ws = new WebSocket("ws://" + window.location.host + "/echo");

ws.onopen = function(event) {
    console.log("WebSocket connection opened.");
};

ws.onmessage = function(event) {
  const payload = JSON.parse(event.data)
  document.getElementById("output").value += payload + "\n";
};

ws.onclose = function(event) {
    console.log("WebSocket connection closed.");
};

function send() {
    var input = document.getElementById("input").value;
    ws.send(JSON.stringify(input))
    document.getElementById("input").value = "";
}

Example run

Welcome to SWI-Prolog (threaded, 64 bits, version 9.1.6)
...
?- working_directory(_,'C:/Users/Groot').
true.

?- [server].
% Started server at http://localhost:8000/
true.

?- 

Using Internet browser
URL: http://localhost:8000/

image

Enter text then press Send

image

Meta predicates

Getting ChatGPT to create this took more than 10 attempts at the prompt. Even gave up on giving ChatGPT the working code first and asking it to create a prompt. It just never seemed to know how to use meta_predicate as a directive which is why the prompt is so specific about that.

Prompt

Create two modules, the first module ‘indirection’ contains the predicate display_list/1 with a goal argument. display_list/1 is a meta_predicate so a meta_predicate directive needs to be used. The second module examples uses use_module for the module ‘indirection’ and contains two predicates, nodes/1 that returns a list, check/0 that calls display_list and passes the goal ‘nodes’. Put the code in a code block for readability.

ChatGPT Completion

%Module 1 - indirection.pl
:- module(indirection, [display_list/1]).
:- meta_predicate display_list(0).

display_list(Goal) :-
  call(Goal, List),
  write(List).

%Module 2 - examples.pl
:- module(examples, [check/0]).
:- use_module(indirection).

nodes(Nodes) :- Nodes = [1, 2, 3, 4, 5].

check :- display_list(nodes).

The first time a reply with working code the meta_predicate was

:- meta_predicate display_list(:).

and this time it is

:- meta_predicate display_list(0).

both ways work.