Options for write_term
[Note: If you have comments please post them at the Prolog Community Discourse for this PIP]
Abstract
Extending the set of options recognized by the standard write_term/2,3 predicate.
Notation and Terminology
Abbreviations for Prolog systems: Ciao (Ciao-Prolog), ECL (ECLiPSe), GP (GNU-Prolog), IF (IF-Prolog), SP (SICStus Prolog), SWI (SWI-Prolog), XSB (XSB-Prolog).
Motivation and Rationale
We suggest a number of additional options for the write_term/2,3 built-in, as an extension of the basic set predefined by ISO-Prolog.
Main section(s)
The options required by the ISO-standard
The ISO-Prolog Standard [STD] defines the following required write-options in its section 7.10.4.
quoted(Bool)
Iff Bool is true each atom and functor is quoted if this would be necessary for the term to be input byread_term/3
. In addition, floats must be printed with sufficient precision to allow them to be read back exactly.
NOTE: In systems with a string data type, print strings in string quotes.ignore_ops(Bool)
Iff Bool is true each compound term is output in functional notation. Neither operator notation nor list notation is used when this write-option is in force. In Corrigendum 3 this was extended to apply to the printing of{}/1
terms too.numbervars(Bool)
Iff Bool is true a term of the form'$VAR'(N)
, where N is an integer, is output as a variable name consisting of a capital letter possibly followed by an integer. The capital letter is the (i+1)th letter of the alphabet, and the integer is j, where i = N mod 26, j = N // 26. The integer j is omitted if it is zero.
NOTE: the standard forbids the traditional feature of allowing N to be an atom which is used as the variable name.variable_names(VNList)
Each variable V is output as the sequence of characters defined by the syntax for the atom A iff a termA=V
is an element of the list VNList. If more than one element applies, the leftmost is used. VNList is a list of termsA=T
with A an atom and T any term, possibly a variable.
NOTE: This option was added with Corrigendum 3 [STD3].
A processor may support one or more additional write-options as an implementation specific feature.
Recommended enhancements
Treatment of unknown/unsupported option shall be according to a Prolog flag
unknown_option
with valueserror|warning|ignore
. ISO requires error, but ignoring or warning is more convenient when using options that are not supported by every implementation. The flag may be module-specific.Allow
option_name
as abbreviation foroption_name(true)
. For examplewrite_term(T, [quoted,ignore_ops])
.If
numbervars(true)
is in effect, and the argument of'$VAR'(N)
is an atom representing a valid variable name, output this name unquoted. This provides backward compatibility with pre-ISO implementations without compromising security.Defaults may be inherited from context settings. If not otherwise specified, the default for boolean options is
false
.
Recommended additional options (by topic)
Whole term layout
max_depth(N) (SP,SWI,ECL,GP,Ciao,XSB)
IfN
is a positive integer, print the term only up to a maximum nesting depth ofN
, and represent more deeply nested subterms as...
. If0
, impose no depth limit.
NOTE: If this option is not given, a default is inherited from a context setting in an implementation-defined manner.portrayed(Bool) (SP,ECL,IF,GP,Ciao,SWI)
Iftrue
, call the user-defined predicateportray/1,2
in the wayprint/1,2
does.
NOTE: This is for systems that support traditionalprint/portray/1
– ISO doesn’t defineprint/portray
at all. ECLiPSe prefersprint/portray/2
with stream argument.priority(Prec) (SP,SWI,GP,Ciao,ECL,XSB)
Prec
is an integer between0
and1200
(default1200
), representing context operator precedence. Can be used to force correct parenthesizing when partial terms are written as arguments of operators. The written term will be enclosed in parentheses if its precedence is higher than Prec.
Term termination
fullstop(Bool) (ECL,SWI)
Iftrue
, terminate the term with a fullstop (a dot followed by blank space), so it can be read back. The blank space after the dot is a newline if thenl(true)
option is present, otherwise a space character. If necessary, an extra space will be inserted before the fullstop, in order to separate it from the end of the term.nl(Bool) (ECL,SWI)
Iftrue
, print a newline sequence (as withnl/1
) after the term. If this is used together with thefullstop(true)
option, this newline serves as the blank space after the fullstop.
NOTE: Some of these seem redundant, since
write_term(T,[nl(true)])
can be written as
write_term(T,[]),nl
. However, in a
multi-threaded context, the two-goal sequence may be
interrupted by other threads printing to the same stream,
while a single write_term goal can easily be implemented
as an atomic operation.
Functor-specific syntax
portable(Bool) (proposal)
Iftrue
, ignore operator declarations and output the corresponding compound terms in functional notation. This is like ISOignore_ops
, except that it retains list notation ([...]
, also for improper lists), brace-terms ({...}
) and infix commas. Comparison of related options:Availability Option pre/in/postfix lists braces comma other ISO <default> a-b [a,b] {c} a,b undef ISO ignore_ops(true) -(a,b) .(a,.(b,[])) {}(c) ,(a,b) undef This PIP portable(true) -(a,b) [a,b] {c} a,b portable RATIONALE: This option is useful when exchanging text between different Prolog contexts, such as modules with different local operator declarations, or different Prolog systems. It leads to a more compact and readable representation than
ignore_ops(true)
and is therefore often preferable. For non-standard Prolog extensions, this option should produce a textual representation that is commonly agreed between different implementations and therefore portable.
Whole term layout
spacing(Atom) (SWI,proposal)
Where to print spaces, with the alternatives- compact: when needed for correct parsing (with some implementation-specific allowance for redundancy)
- next_argument: also after the comma separating structure or list arguments
- generous: also after prefix, around infix and before postfix operators
partial(+Bool) (SWI,this PIP)
Iftrue
, insert a single space ahead of the printed term, if this is necessary to ensure token separation from previously printed text.
RATIONALE: together with thepriority(Prio)
option, this helps to correctly print subterms in the context of larger terms.cycles(Bool) (proposal based on SP,SWI,ECL)
Iftrue
, detect cyclic subterms and print them using an implementation-defined syntax. Iffalse
(or if the system does not support cycle syntax), the result depends on themax_depth
-option: if there is a depth limit, cyclic subterms shall be expanded until the depth limit for the whole term is reached. If there is no depth limit, cycles shall be detected bywrite_term/2,3
and an error raised.
The default value of this option is implementation-defined.Options max_depth(0) max_depth(D), D>0 cycles(false) Detect cycles and raise error Expand cycles up to depth D cycles(true), cycle syntax not supported Detect cycles and raise error Expand cycles up to depth D cycles(true), cycle syntax supported Detect cycles and print using cycle syntax Detect cycles and print using cycle syntax NOTE: The concrete syntax for cyclic (or other shared) subterms is the subject of a separate PIP (0109).
Subterm-type-specific options
float_precision(+Prec) (XSB)
Prec
is the number of significant (decimal) digits with which floating-point numbers are printed. The default is implementation-defined. If0
is specified, the system prints as many digits as are needed to read the number back exactly (thequoted(true)
option also enforces this and overrides any different setting).integer_base(+Base)
Print integers in the given base (radix).Base
is an integer in the range2..36
, or one of the atomsdec
,bin
,oct
,hex
. By default, numbers are printed with a prefix: ForBase
in2..9,11..36
, the prefix isBase'
, forbin
,oct
andhex
the prefixes are0b
,0o
and0x
respectively, and no prefix for10
anddec
.
NOTE: similar toradix(Radix)
in XSB.text_max(+Length)
Truncate text (atoms and strings) afterLength
characters. Don’t truncate if0
(default). Whether and how the abbreviation is indicated is left implementation-defined. This applies to both quoted and unquoted output.
Discretionary options (alphabetic)
The options here did not warrant an explicit general recommendation. However, they are meaningful and implemented in selected systems, and should be used as a guideline for adoption in other systems.
anonymous(+Vars) (proposal)
Print the given variables as_
(anonymous). Can be used together with thevariable_names
option.as(Kind) (ECL)
Assume that the printed term is of the given Kind, where Kind is one ofclause
,goal
, orterm
.
Note: In ECL, this selects the appropriate write transformations, but it could also be used to control portraying and indentation.atom_quoting(+When)
Specify how atoms are quoted ifquoted(true)
. When is one of- when_needed: when necessary for correct parsing (default)
- when_non_ascii: in addition, quote atoms that contains non-ASCII characters
- always: quote all atoms, regardless of the characters they contain (useful for non-Prolog readers)
attributes(Atom) (ECL,SWI)
Determines how variable attributes are printed. It is common to print them in curly braces after the variable they belong to, and if necessary qualify them with an attribute name. Options (default is inherited from a global setting) are:- none (ECL) or ignore (SWI): do not print attributes
- dots (SWI): print
{...}
- full (ECL) or write (SWI): print the attributes as subterms surrounded by curly braces
- pretty (ECL): use a per-attribute print handler mechanism to transform before printing, or suppress.
- portray (SWI): use a per-attribute portray mechanism.
character_escapes(+Bool) (SWI)
This controls the way nonprintable characters are displayed in quoted text.
NOTE: It is not clear which functionality should be supported in this respect. Behaviour should also depend on the stream’s character encoding and the Prolog dialect in use.float_format(Atom) (SP,SWI)
This is sensible, but defined in terms of non-standard format/2.float_style(+Style) (XSB)
Select a C-printf-like style, one off
,e
,g
, orh
(full precision for reading back). Upper case variants print any letters in the number notation in upper case (E
instead ofe
).flush(Bool) (ECL)
If true, flush the stream (as withflush[_output]/1
) after the term hash been printed.integer_prefix(+Bool)
If true (default), prefix integers with a string indicating the base. This is eitherBase'NumberInBase
(if Base is an integer), or the ISO base prefix (if Base is one of the atomsdec
,bin
,oct
,hex
).integer_format(Atom) (SWI)
This is sensible, but defined in terms of non-standard format/2.module(Module) (SWI)
Workaround for passing context module.portray_goal(:Goal) (SWI)
Callcall(Goal,SubTerm,WriteOptions)
for every subterm. Like portray, if this fails, print normally, otherwise consider subterm printed. This can be used to implement an interface to format/3, for example.
NOTE: this seems to be a heavy-weight mechanism because while a term is traversed by write_term, a portray-goal is blindly called for every subterm, even when it is only needed for a subset of them. It will often be simpler and clearer to write the whole term traversal in user code, printing subterms via built-ins.truncated(-Bool)
Return a boolean indicating whether the printed term was abbreviated due to themax_depth
options.variables(Method) (ECL)
How to print variables (not all can be reliably read back):- default: print variables using their source name, if available, else a system-generated name.
- raw: print all variables using a system-generated name.
- full: print variables using their source name, if available, followed by a unique number, e.g. Alpha_132.
- anonymous: print every variable as a simple underscore.
NOTE: This is orthogonal to the
variable_names
options, and mainly applicable to systems that can retain source variable names (such as ECL). SWI supports a feature to detect singletons and treat them specially.
Deprecated/legacy Options (alphabetic)
The following options occur in Prolog systems, but are either superseded by the recommendations above, or are considered out of scope for write_term’s core functionality.
brace_terms(Bool) (SWI)
If true (default!) write {}(X) as {X}.
NOTE: This is subsumed by the ISOignore_ops
and the proposed ‘portable’ option.compact(Bool) (ECL)
Subsumed byspacing(compact)
.dotlists(Bool) (ECL,SWI)
If false (default), write lists in the common square bracket notation, e.g.[1, 2]
. If true, write lists in the dot functor notation, e.g..(1,.(2,[]))
. This is subsumed by the ISOignore_ops
option.
Note: SWI also has no_lists(Bool) for alternative list functor.float_specifier(Spec) (XSB)
Floats in XSB are printed using underlying C routines. The allowed values are g,G,f and F. The default value is g.
NOTE: included above asfloat_style(Spec)
float_width(Width) (XSB)
Width must be an integer between 1 and 17, and this number determines the minimum width precision with which a floating point number is displayed. For instance, a width of 2 ensures that a floating point number is always displayed with a decimal value. The default value is 2.
NOTE: we recommend to deal with width padding on a global level for the whole term, not per subtermindented(Bool) (SP)
The term is printed with the same indentation as is used by portray_clause/1 and listing/[0,1].
NOTE: This is specific to clauses. We recommend to keep write_term basic, and leave complex formatting to specialised routines.legacy_numbervars(Bool) (SP)
Likenumbervars
, but with the more permissive pre-ISO convention for printing variable names: if the argument of'$VAR'(N)
is an atom or code list, these characters are written instead of the term.
NOTE: we recommend a limited form of this behaviour as defaultmaxdepth(N) (IF):
The same asmax_depth(N)
.maxdepth(N,TermAbbrev,ListAbbrev) (IF)
Allows for specifying the atoms that are used to abbreviate the omitted subterms.
NOTE: if necessary, use separate options to specify the abbreviationsnamevars(Bool) (GP)
A term of the form’$VARNAME’(Name)
, where Name is an atom respecting the syntax of variable names, is output as a variable name.
NOTE: we recommend this as$VAR(Name)
behaviouroperators(Bool) (ECL)
operators(false)
is essentially equivalent toportable(true)
.precedence(Pred) (ECL)
A synonym forpriority(Prec)
.radix(Radix) (XSB)
Ensures that integers are printed with radix Radix. Radix can bedecimal
,hex
oroctal
. The default is decimal. The functionality is covered byinteger_base(Base)
.space_args(Bool) (GP)
Subsumed byspacing(Atom)
.
Relationship with other write-predicates
Other predicates of the write-family (both ISO- and de-facto-standard) can be defined in terms of write_term/3 with specific option lists:
write(S, T) :-
write_term(S, T, [numbervars]).
writeln(S, T) :-
write_term(S, T, [numbervars, nl]).
writeq(S, T) :-
write_term(S, T, [quoted, numbervars, max_depth(0), cycles]).
write_canonical(S, T) :-
write_term(S, T, [ignore_ops, quoted, numbervars, max_depth(0), cycles]).
print(S, T) :-
write_term(T, [portrayed, numbervars]).
display(S, T) :-
write_term(T, [ignore_ops]).
Guidelines for adding options
The functionality of write_term overlaps with the functionality of format/printf. Trying to support all format/printf functionality in write_term may lead to a confusing proliferation of options, and multiple ways of doing the same thing. This might be kept in check by considering that * format/printf is about embedding one (or multiple) small (often atomic) Prolog terms into a printed text string. The focus is on how each embedded term is laid out individually. The reader is probably a human end user unfamiliar with Prolog terms. * write_term is about printing a single (possibly complex) Prolog term. The focus is on globally controlling the layout of an unknown number of subterms of varying types. The reader is probably a programmer or program familiar with Prolog terms.
Related work
See the various system’s reference manuals, and the PIP for format/2,3.
References
- [STD] International Standard ISO/IEC 13211-1 : 1995 Programming Languages - Prolog
- [STD3] ISO/IEC 13211-1:1995 TECHNICAL CORRIGENDUM 3 from 2017-07
Copyright
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.