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.
NOTE: SWI’s version does not affect lists and brace-terms.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)
If MaxDepth is a positive integer, print the term only up to a maximum nesting depth of MaxDepth, 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)
If true, call the user-defined predicateportray/1,2
in the wayprint/1,2
does.
NOTE: This is for systems that support traditional print/portray/1 – ISO doesn’t define print/portray at all. ECLiPSe prefers print/portray/2 with stream argument.priority(Prec) (SP,SWI,GP,Ciao,ECL,XSB)
Prec is an integer between 0 and 1200 (default 1200), 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)
If true, 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)
If true, 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.
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 thanignore_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.
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|
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)
If true, 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(Style) (this PIP, partially in SP and SWI)
where Style is one of- false: don’t detect cycles (default, because cycle detection can be expensive)
- @** or true** (SP/SWI
compatibility): print term as as
@(Template, Substitutions)
, where Substitutions is a listVar=Value
- attribute: print cycles in ECL
value-attribute notation
X{value:SubtermContainingX}
- error: throw error for cyclic terms
TBD: The true-default in SWI is surprising, as the @/2 syntax is nonstandard and conflicts with other uses of this functor. It goes with a corresponding option in
read_term/2,3
for reading this @/2 syntax.
Subterm-type-specific options
float_precision(+Precision) (XSB)
Interpreted according to style, e.g. number of digits after the decimal point forfloat_style(f)
, or number of significant digits forfloat_style(g)
.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
).text_max(+Length)
Truncate text (atoms and strings) after Length characters. Don’t truncate if0
(default). Whether and how the abbreviation is indicated is left implementation-defined.atom_quoting(+When)
Specify when to quote atoms. When is one of- never: never quote atoms (default)
- when_needed: when necessary for correct parsing
- when_needed_or_non_ascii: always quote if the atom contains non-ASCII characters
- always: always quote atoms, useful
for non-Prolog readers
text_escapes(+What) (defer to discussion about character sets)
Whether to print nonprintable characters as escape sequences in quoted text. What is one of- all: escape all nonprintable characters (default)
- most: escape all but newlines and tabs
- none: print all characters as themselves
TBD: Discuss escape option set
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.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.
TBD: agree on the names. Dots and portray options not suitable for ECL.
float_format(Atom) (SP,SWI)
This is sensible, but defined in terms of non-standard format/2.flush(Bool) (ECL)
If true, flush the stream (as withflush[_output]/1
) after the term hash been printed.integer_base(+Base)
Print integers in the given base. Base is an integer in the range 2..36, or one of the atomsdec
,bin
,oct
,hex
.
NOTE: similar toradix(Radix)
in XSB.integer_prefix(+Bool)
If true, 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.
TBD: 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, but usually only needed for a subset of them. On the other hand, if some action is needed for every subterm, 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.
TBD: Discuss options. Source names are probably ECL-specific. SWI can detect singletons and treat 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)
This is like ISOignore_ops
, except that it does not affect lists (which is rarely desirable), but does affect all other syntactic sugaring (operators, braces, extensions such as array subscripts, apply syntax).
NOTE:operators(false)
can be usually be replaced byignore_ops(true)
orportable(true)
.precedence(Pred) (ECL)
A synonym forpriority(Prec)
.radix(Radix) (XSB)
Subsumed byinteger_base(Base)
. Ensures that integers are printed with radix Radix. Radix can bedecimal
,hex
oroctal
. The default is decimal.
NOTE: included above asinteger_base(Base)
space_args(Bool) (GP)
Subsumed byspacing(Atom)
.
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 a unknown number of subterms of varying types. The reader is probably a programmer or program familiar with Prolog terms.
Implementation
details for implementation, systems that support it, etc.
Related work
pointers to discussions and other PIPs
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.