I/O Builtins¶
Clausal provides built-in predicates for formatted output, term-to-string conversion, and f-string interpolation. Whether you need to print debug output, format a table, or build strings from logic variables, the I/O builtins have you covered. For calling Python functions directly, see Python Integration.
Quick Example¶
Output Predicates¶
write/1 vs writeln/1 vs print_term/1¶
All three write a term to stdout, but differ in formatting:
| Builtin | Newline? | Strings | Terms |
|---|---|---|---|
write/1 |
No | Quoted ("hello") |
functor notation |
writeln/1 |
Yes | Quoted ("hello") |
functor notation |
print_term/1 |
No | Unquoted (hello) |
str() representation |
# write: no newline, quoted strings
Test("write") <- (write("hello"), write(" "), write("world"), nl())
# writeln: like write + nl
Test("writeln") <- writeln("hello")
# print_term: unquoted, str()-style
Test("print term") <- print_term("hello")
when to use which:
write/writeln— standard output, preserves Clausal term syntaxprint_term— human-readable output (no quotes around strings)write+nl— when you need precise control over newlines
nl/0¶
write a newline character:
tab/1¶
write N spaces:
String Conversion¶
write_to_string/2¶
write_to_string(Term, String) — unify String with the write representation of Term (quoted strings, functor notation):
format_pair(K, V, S) <- write_to_string(K - V, S)
Test("write to string") <- (
format_pair("name", "alice", S),
nonvar(S)
)
term_to_string/2¶
term_to_string(Term, String) — unify String with the str() representation of Term (unquoted strings):
label(X, S) <- term_to_string(X, S)
Test("term to string int") <- (label(42, S), S == "42")
Test("term to string str") <- (label("hello", S), nonvar(S))
write_to_string vs term_to_string:
| Input | write_to_string | term_to_string |
|---|---|---|
42 |
"42" |
"42" |
"hello" |
quoted | unquoted |
[1, 2] |
"[1, 2]" |
"[1, 2]" |
Use term_to_string when building human-readable strings. Use write_to_string when you need a representation that could be read back.
F-String Support¶
in_ .clausal files, f-strings build strings with logic variable interpolation. Variables are automatically dereferenced before the f-string is evaluated:
describe(NAME, AGE, S) <- (
S is f"Name: {NAME}, Age: {AGE}"
)
Test("describe") <- (
describe("Alice", 30, S),
S == "Name: Alice, Age: 30"
)
Expressions in F-Strings¶
F-strings support arbitrary Python expressions inside {}:
summarize(XS, S) <- (
length(XS, N),
S is f"List has {N} element(s)"
)
Test("summarize") <- (
summarize([1, 2, 3], S),
S == "List has 3 element(s)"
)
Multi-Variable F-Strings¶
All logic variables referenced in the f-string are dereferenced:
full_name(FIRST, LAST, S) <- (
S is f"{FIRST} {LAST}"
)
Test("full name") <- (
full_name("Alice", "Smith", S),
S == "Alice Smith"
)
Deferred Evaluation¶
F-strings use deferred evaluation — the f-string is evaluated at search time, after variables are bound. This means f-strings work correctly with backtracking:
color("red"),
color("green"),
color("blue"),
describe_color(S) <- (
color(C),
S is f"The color is {C}"
)
Test("deferred f-string") <- (
describe_color(S),
S == "The color is red"
)
Formatting Patterns¶
Printing a List¶
String Building with term_to_string¶
Building Strings with foldl¶
Use == with + to concatenate strings inside a foldl closure:
concat_all(XS, RESULT) <- (
foldl(
((E, A, R) <- (R == A + E)),
XS, "", RESULT
)
)
Test("concat all") <- (
concat_all(["a", "b", "c"], R),
R == "abc"
)
Clause Inspection¶
| Builtin | Arity | Description |
|---|---|---|
listing |
1 | listing(Pred) — print all clauses of a predicate to stdout |
portray_clause |
1 | portray_clause(Term) — pretty-print a term with indentation |
Examples¶
# List all clauses for a predicate:
debug_fib <- listing(fib)
# Pretty-print a complex term:
show_deep(TERM) <- portray_clause(TERM)
listing accepts a predicate class or instance. It prints a header with clause count, then each clause in head <- (body). format.
Var Display¶
Logic variables have __str__ and __format__ methods (in the C extension) that auto-deref for display:
- Bound var: displays the bound value
- Unbound var: displays
_N(unique numeric ID)
This means f"{X}" and write(X) show the value if bound, or a placeholder if unbound. This works in both .clausal files and Python code:
from clausal.logic.variables import Var, Trail, unify
v = Var()
print(f"Unbound: {v}") # _42 (placeholder)
trail = Trail()
unify(v, "hello", trail)
print(f"Bound: {v}") # hello
Gotchas¶
- write quotes strings, print_term does not. If your output has unwanted quotes, switch to
print_termor use f-strings. - F-strings evaluate at search time, not at parse time. An f-string with an unbound variable will show the Var placeholder (
_N), not raise an error. - nl/0 takes no arguments —
nl()notnl(1). Usetab(N)for spacing.
Test coverage
Tests are in tests/test_io.py (43 tests) and tests/test_listing.py (13 tests).
- Var display:
__str__,__format__, bound/unbound, nested - write/writeln/print_term: atoms, numbers, strings, compounds, lists, vars
- nl/tab: output formatting
- write_to_string/term_to_string: term conversion to string
- F-string integration: variable interpolation, multiple vars, expressions
- listing/1: facts, rules, no-clauses, instance→class resolution, error handling
- portray_clause/1: simple terms, lists, nested structures, unbound vars
See also: Python Integration — using ++() escape for Python calls inside logic goals.
See also: Lambdas — goal closures used with foldl and other higher-order predicates.