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 or struct? 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 blank between tag and {). - ECLiPSE: foo {} (foo as prefix operator) can be read differently than foo{}

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: Exceptions
    • instantiation 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’s dict_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) or Emp.[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 to tag(v1,...,vn)
  • k_i of tag expands to i
  • property(arity) of tag expands to n
  • property(functor) of tag expands to tag
  • 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 to tag(V1,...,Vn), values for missing keys are unbound.
  • tag${/} expands to tag/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 like get_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)


The Prolog Implementers Forum is a part of the "All Things Prolog" online Prolog community, an initiative of the Association for Logic Programming stemming from the Year of Prolog activities.