Terms with named arguments (static dicts)
[Note: If you have comments please post them at the Prolog Community Discourse for this PIP]
Abstract
A notation for compound terms with named arguments. This notation enables the use of field names in compound terms, rather than positional arguments. Note that this is not a new data type.
Notation and Terminology
Abbreviations for Prolog systems: Ciao (Ciao-Prolog), ECL (ECLiPSe), SWI (SWI-Prolog), XSB (XSB-Prolog).
Motivation and Rationale
It is intended as a notation to make programs more readable and easier to modify, without changing Prolog semantics or compromising efficiency, as it can be implemented by macro expansion.
Note: In this PIP we deal with terms with named arguments, a.k.a. static dicts. See PIP 0102 for the PIP for dynamic dics.
Main section(s)
This document has four parts:
- Static dictionary syntax
- Notations resolved statically
- Other functionality
TBD: Some be discussed Requirements:
- Declarations are local to the module (like operators)
and affect the parsing context (i.e. when enabled, the
Prolog parser must be able to read terms like
f{k1:T1, ..., kn:Tn}
). - The structure/argname table must be stored at least statically.
- Macro expansion must translate this notation to compound terms with no runtime penalty.
- (optional) The structure table is preserved at runtime for dynamic lookups.
- (optional) The structure name can be local to the module to avoid ambiguities.
- (optional) Functional notation can be used to simplify the notation.
TBD: where does this expansion happen? program text? term expansion? IO read too?
TBD: portray?
Static dictionary syntax
The declaration:
:- argnames tag(k1, ..., kn).
TBD:
argnames
orstruct
? propose one name, allow others for compatibility (argnames or struct?)
maps every tag{k1:v1, ..., kn:vn}
occurence (where some ki
may be missing) to
tag(v1, ..., vn)
(where missing
vi
are left as unbound variables).
Example (borrowed from ECLiPSe documentation):
:- argnames book(author, title, year, publisher).
then subsequently book/4
-terms can be
written as follows:
book{}
book{title:'tom sawyer'}
book{year:1886, title:'tom sawyer'}
which will be completely equivalent to the usual:
book(_, _, _, _)
book(_, 'tom sawyer', _, _)
book(_, 'tom sawyer', 1886, _)
NOTE on syntax: -
book{...}
syntax can be done in ISO Prolog if:- argnames
decl automatically declares a prefix operator (very low precedence) - some implementations may allow this syntax without declaring an operator (because in this PIP we recommend not using a blankbetween tag and
{
). - ECLiPSE:foo {}
(foo
as prefix operator) can be read differently thanfoo{}
The advantage is that the order and position of the fields or the arity of the whole structure do not have to be known and can be changed by just changing the initial declaration.
Example (based on Ciao argnames):
Extracting the title of a book:
B = book{title:Title} % same as B = book(_, Title, _, _)
Unifying fields of a book:
B = book{},
B = book{title:'tom sawyer'},
B = book{year:1886, title:'tom sawyer'}.
TBD: Clashes between modules? (use different/prefixed functor names in translation?) - local vs exported: ECLiPSe: like
op
(include in module that imports)
Notations resolved statically
TBD: decide syntax? make it optional?
Extracting argument index of a field (ECLiPSe)
The argument index of a field in a structure can be
obtained using a term of the form
FieldName of StructName
.
Example: arg(3,B,Y)
can
be written as arg(year of book, B, Y)
Pros and cons: - (cons) Jan: error prone (B may not be a book) - (pros) Joachim: simple solution - (cons) everyone: it takes of/2 operator
Safer:
functor(B, property(name) of book, property(arity) of book), arg(year of book, B, Y)
TBD: add additional predicates?
Suggestion: use only when needed.
E.g., ECLiPSe sort/4
where you specify the
argument and the sorting direction.
Arity of a structure (ECLiPSe)
The arity of the structure can be obtained using a term
of the following form:
property(arity) of StructName
.
Example:
property(arity) of book
is expanded to the
integer 4.
Functor of a structure (ECLiPSe)
The functor of the structure can be obtained using a
term of the following form:
property(functor) of StructName
.
Example:
property(functor) of book
is expanded to
book/4
.
Other functionality
Dynamic declaration of argnames
TBD: Discuss it
Similary to operators, argnames can be declared dynamically.
argnames(++) is det
: Exceptionsinstantiation fault
: Struct is not ground.type error
: Struct is neither variable nor structure.
TBD: if duplicated, error or overriding (like operators)?
Example:
?- argnames(person(name,address,age)).
?- John = person{age:30, name:john},
John = person{age:A},
arg(name of person, John, N).
John = person(john, _146, 30)
A = 30
N = john
yes.
?- N is (property(arity) of person) + 1.
N = 4
yes.
?- PersonStructure = (property(functor) of person).
PersonStructure = person/3
yes.
Nested structures
TBD: Discuss it (agreed that it opens a can of worms)
ECLiPSe structures can also be declared to contain other structures, e.g.
:- local struct(film(based_on:book,director,year)).
This allows the fields of book to be accessed as if they were fields of film.
This is a very convenient notation but raises some issues, as one may also want to use some path navigation (rather than exposing the field names directly), or declare the structures of normal compound terms with positional arguments. At the end, this is covered by a static type system.
Suggestion: do not include the the proposal.
TBD: JavaScript inspired version of
$~
?B2 = book{...B, title: 'a boring book'}
SWI’sdict_put
operation
Dynamic lookups
TBD: relate with current_struct/1 ECLiPSe?
TBD: Discuss it
TBD: useful for portray, translation to dynamic dicts (Jan)
Accessing the positional encoding of an argument with
arg(year of book, B, Y)
requires knowing
both the field name (year
) and the structure
name (book
).
Since the structure name is known at runtime, this can also be done dynamically (at some cost, which can be reduced or eliminated with static analysis).
Suggestion: useful in practice, link to dynamic dictionaries.
Field access notation
TBD: Keep it outside the proposal
Functional notation can be used to access fields of structures.
Subscript syntax in ECLiPSe, limited to
is/2
:
?- Emp = employee{name:john, salary:2000},
Cost is 5 * Emp[salary of employee].
Alternatives: - Ciao (and SWI for dynamic dicts?) can evaluate field access in arbitrary term positions.
Allow accessor notation, possibly using paths
foo.bar.baz
Annotate field or annotate structure term? (more conventional)
(Emp as employee).salary
or(Emp as employee)[salary]
.Annotate field or annotate structure term? (interesting)
Emp.(salary of employee)
orEmp.[salary of employee]
or
APPENDIX 1: Existing practice
ECLiPSe
See https://www.cs.nmsu.edu/~ipivkina/ECLIPSE/doc/applications.pdf https://www.eclipseclp.org/doc/bips/kernel/syntax/struct-1.html
Called “Named structures”.
Declaration:
:- [local/export] struct(tag(k1, ..., kn)).
Usage:
tag{k1:v1, ..., kn:vn}
expands totag(v1,...,vn)
k_i of tag
expands toi
property(arity) of tag
expands ton
property(functor) of tag
expands totag
- subscript syntax
Term[arg1 of tag]
arg(arg1 of tag, Term, Arg)
Term[arg1 of tag]
Ciao
See https://ciao-lang.org/ciao/build/doc/ciao.html/argnames_doc.html
Declaration:
:- argnames tag(k1, ..., kn).
Usage:
tag${k1=>V1,...,kn=>Vn}
expands totag(V1,...,Vn)
, values for missing keys are unbound.tag${/}
expands totag/N
tag${argnames}
expands to list of keys ([k1,...,kn]
)- (expanded as goal)
tag${...,K=>V,...}
with variables in keys are expanded to runtime lookups (similar to likeget_dict/3
) - (expanded as goal)
$~(T, tag${..., k=>V, ...}, NewT)
replace values of specific tags
SWI-Prolog (no dicts of this type)
See https://www.swi-prolog.org/pldoc/man?section=record
Declaration:
:- record tag(arg1[:type1[=default]], arg2, ...)
Access:
tag_arg1(Term, Arg)