I/O Operations in Prolog
Learn input and output in Prolog - console output, reading from stdin, file streams, and reading and writing terms with Docker-ready examples
Input and output occupy an unusual place in Prolog. The language is fundamentally declarative — you describe relationships and let the resolution engine search for solutions. But I/O is inherently about side effects: printing to a terminal, consuming a line of input, writing bytes to a file. These actions happen once, in order, and can’t be backtracked away.
Prolog handles this tension by treating I/O predicates as goals that always succeed (once) while producing their effect as a side effect of proof. When you write write(hello), nl, Prolog proves write(hello) — printing hello along the way — then proves nl, printing a newline. Because these goals are deterministic, backtracking into them does nothing useful, so they behave much like statements in an imperative language.
What makes Prolog’s I/O distinctive is that its natural unit is the term, not the string. Alongside character- and line-oriented predicates, Prolog offers read/1 and write/1, which serialize and parse full Prolog terms — compound structures, lists, numbers, and atoms — using the same syntax you write in source files. This makes a Prolog program’s data format and its code format one and the same.
This tutorial covers console output, formatted output with format/2, reading from standard input, working with file streams, and the term-oriented I/O that is unique to logic programming. All examples run on SWI-Prolog via Docker.
Console Output Predicates
Prolog provides several predicates for writing to the current output stream. Each treats quoting and spacing differently.
Create a file named output.pl:
| |
write/1prints a term without quoting. The atom'hello world'prints ashello world, dropping the quotes.writeq/1prints a term so it can be read back byread/1. Becausehello worldcontains a space, it prints with quotes:'hello world'.writeln/1is a convenience predicate that writes a term followed by a newline.nl/0writes a single newline;tab/1writes N spaces.
Formatted Output with format/2
For anything beyond simple values, format/2 is the workhorse. It takes a format string with ~-directives and a list of arguments, similar in spirit to C’s printf but with directives suited to Prolog.
Create a file named formatted.pl:
| |
The directives used here:
~wwrites an argument (likewrite/1).~dprints an integer;~Nfprints a float with N decimals (~4f→ four places).~Nrprints an integer in radix N (~16r→ hexadecimal).~cprints the character for a character code (65→A).~tand~N|together create column alignment:~tinserts fill (spaces by default) and~10|sets a column stop at position 10, so each label is padded out to a fixed width.
Reading Input from Standard Input
Prolog reads user input from the user_input stream. read_line_to_string/2 reads one line as a string, which you can then convert to a number or other term.
Create a file named input.pl:
| |
Here read_line_to_string/2 binds Name to the text of the first input line (as a string, without the trailing newline). number_string/2 parses the second line into an integer, and is/2 evaluates the arithmetic. Because the prompts are printed with write/1 and no newline, they appear directly before each result when input is piped in.
Writing and Reading Files (Line-Oriented)
File I/O in Prolog goes through streams. You open/3 a file to get a stream handle, read or write through it, then close/1 it. The idiomatic way to guarantee the stream is closed — even if a goal fails or throws — is setup_call_cleanup/3.
Create a file named file_io.pl:
| |
Notice that format/3 (and write/2, nl/1, etc.) take an explicit stream as the first argument, directing output to the file instead of the console. Reading loops via recursion — Prolog’s substitute for a while loop — until read_line_to_string/2 returns the atom end_of_file. The ( Cond -> Then ; Else ) construct is Prolog’s if-then-else.
Reading and Writing Terms
The most Prolog-native form of I/O reads and writes terms rather than raw text. writeq/2 writes a term in a form that can be parsed back, and read/2 reads a term terminated by a period. This lets a file double as a serialized data structure.
Create a file named terms_io.pl:
| |
Each line written to facts.pl is a valid Prolog term followed by a period — exactly the syntax Prolog uses in source files. When reading, read/2 parses each term back into a structured point(X, Y), which unifies cleanly so we can pull out the coordinates and compute with them. At end of file, read/2 yields end_of_file. This term round-tripping is what makes Prolog’s data and code representations identical.
Running with Docker
| |
The -q flag suppresses the SWI-Prolog banner. Because the current directory is mounted at /app, files written by the programs (languages.txt, facts.pl) appear in your local folder.
Expected Output
Running output.pl:
Plain write: hello world
Quoted writeq: 'hello world'
writeln adds a newline for you
Indented: after 4 spaces
Running formatted.pl:
Name: alice, Age: 30
Pi to 4 places: 3.1416
Hex of 255: ff
Char from code 65: A
7 items in stock
Item Qty
apples 12
pears 5
Running input.pl with the piped input Ada then 21:
Enter your name: Hello, Ada!
Enter a number: 21 doubled is 42
Running file_io.pl:
Wrote 3 lines to languages.txt
Reading languages.txt back:
prolog 1972
erlang 1986
python 1991
Running terms_io.pl:
Read term: point(1,2)
Sum of coords: 3
Read term: point(3,4)
Sum of coords: 7
Key Concepts
- I/O predicates are deterministic side effects. They succeed once and produce their effect during proof; backtracking cannot undo output that has already happened.
write/1vswriteq/1. Usewrite/1for human-readable output andwriteq/1(orwriteq/2) when the output must be parseable back into a term byread/1.format/2is the flexible workhorse. Directives like~w,~d,~Nf,~Nr, and~chandle common formatting, while~tand~N|provide column alignment.- Streams model files.
open/3returns a stream handle; predicates likewrite/2andformat/3take that stream as an explicit first argument to redirect output. setup_call_cleanup/3guarantees cleanup. It ensuresclose/1runs whether the body succeeds, fails, or throws — the safe way to manage file handles.- Recursion replaces loops. Reading a whole file is expressed as a recursive predicate that stops when the read predicate returns
end_of_file. - Term I/O is uniquely Prolog.
read/1andwrite/1serialize and parse full terms using Prolog’s own syntax, so files and source code share one representation. - Convert input explicitly. Line reads produce strings; use
number_string/2(oratom_number/2,term_string/2) to turn text into numbers or terms before computing.
Comments
Loading comments...
Leave a Comment