must_be(record(my_record_name))
.Did you know ... | Search Documentation: |
library(record): Access named fields in a term |
The library library(record)
provides named access to
fields in a record represented as a compound term such as point(X,
Y)
. The Prolog world knows various approaches to solve this
problem, unfortunately with no consensus. The approach taken by this
library is proposed by Richard O'Keefe on the SWI-Prolog mailinglist.
The approach automates a technique commonly described in Prolog text-books, where access and modification predicates are defined for the record type. Such predicates are subject to normal import/export as well as analysis by cross-referencers. Given the simple nature of the access predicates, an optimizing compiler can easily inline them for optimal performance.
A record is defined using the directive record/1. We introduce the library with a short example:
:- record point(x:integer=0, y:integer=0). ..., default_point(Point), point_x(Point, X), set_x_of_point(10, Point, Point1), make_point([y(20)], YPoint),
The principal functor and arity of the term used defines the name and arity of the compound used as records. Each argument is described using a term of the format below.
<name>[:<type>][=<default>]
In this definition, <name> is an atom defining the
name of the argument,
<type> is an optional type specification as defined by must_be/2
from library library(error)
, and <default>
is the default initial value. The
<type> defaults to any
. If no default
value is specified the default is an unbound variable.
A record declaration creates a set of predicates through term-expansion. We describe these predicates below. In this description, <constructor> refers to the name of the record (`point’in the example above) and <name> to the name of an argument (field).
library(option)
.
:- record Spec, ...
is used to define access
to named fields in a compound. It is subject to term-expansion (see
expand_term/2)
and cannot be called as a predicate. See
section A.48 for details.must_be(record(my_record_name))
.This implementation seems to be replaceable by the built-in SWI-Prolog dict implementation.
The dict doesn't have a declarative part and doesn't generate specialized predicates at compile time; all data structure manipulations are done using generic predicates.
Note that the naming can become confusing.
:- record vertex_info( vertex_name:atom, reached_from:atom, local_cost:positive_integer, overall_cost:positive_integer, state:oneof([visit_next,probed,visited]), hops:positive_integer ).
Then:
?- default_vertex_info(R). R = vertex_info(_14984,_14986,_14988,_14990,_14992,_14994).
Access/Unify via dedicated predicate:
?- default_vertex_info(R),vertex_info_vertex_name(R,alpha). R = vertex_info(alpha,_16646,_16648,_16650,_16652,_16654).
Access/Unify via generic predicate taking a field name:
?- default_vertex_info(R),vertex_info_data(vertex_name,R,alpha). R = vertex_info(alpha,_20952,_20954,_20956,_20958,_20960).
Access non-existing field:
?- default_vertex_info(R),vertex_info_data(no_such_field,R,alpha). false.
record/1 can only be used in a directive.
Are they not implemented?
Expected to work:
?- default_vertex_info(R),vertex_info_data(state,R,visited). R = vertex_info(_2792,_2794,_2796,_2798,visited,_2802).
Expected to fail or throw because foo
is not a legal value for state
?- default_vertex_info(R),vertex_info_data(state,R,foo). R = vertex_info(_3960,_3962,_3964,_3966,foo,_3970).
Expected to fail or throw because foo
is not a legal value for hops
?- default_vertex_info(R),vertex_info_data(hops,R,foo). R = vertex_info(_5126,_5128,_5130,_5132,_5134,foo).
On second thought, maintaining the type checks would be nontrivial. Unification makes it so that the integrity checks must be triggered at every further instantiation of a field (which can happen at anytime, not only when one of the manipulation predicates is called). If the integrity checks are local to each variable that's still fine (attributed variables can do that), but once they have to check relationships between several fields, this can become very unwieldy. (Imagine an RDBMS where record values can instantiate from NULL to something else even no query is currently running! I guess the best one can do in Prolog is perform checks at certain points or force groundedness of complex data structures.
A predicate which joins returns the names of record fields should be added.
I suppose all the predicates generated by the record
declaration need to be declared explicitly for export in the module declaration (I need to test this).
It is unfortunate that the prefix set_
is used both for predicates which modify the record destructively:
And those which weave the record accumulator style
fields(+Fields, +Record0, -Record)
fields(+Fields, +Record0, -Record, -RestFields)
field(+Field, +Record0, -Record)
Maybe the latter 4 should be named "uptick_" instead of "set_"