62 Parameterised Attributes

Predicates indexed by parameters

An attribute may take parameters:


value ScalarField
{
    Real val(Real x, Real y);
}

Although the syntax resembles a method in an object-oriented language, it does not declare a method or function. val is a base parameterised attribute. For each ScalarField instance and each pair of coordinates, it introduces a base unary predicate whose extension forms part of the information held by that instance.

Given s:ScalarField, x:Real and y:Real, the expression s.val(x,y) denotes a unary predicate belonging to the domain s, whose extension contains values of type Real. A parameterised attribute is therefore a family of unary predicates indexed by its parameter values.

Cardinality constraints

Cardinality constraints apply to parameterised attributes just as they apply to regular attributes:


entity Supplier
{
    City city(Country country)*;
}

For each supplier and each country, the extension of city(country) may contain zero or more cities. More generally, for each instance of the containing type and each combination of parameter values, the resulting predicate extension must satisfy the attribute's cardinality constraint.

With no cardinality qualifier, the extension has cardinality exactly one. Thus the ScalarField declaration requires:

    ∀x:Real ∀y:Real |{ V:Real | s.val(x,y)(V) }| = 1

Enum parameters

An enum type may be used as a parameter type:


enum Material
{
    wood,
    plastic,
    metal
}

entity Object
{
    Real mass(Material material);
}

Because Material is finite, mass(material) is extensionally like declaring separate attributes massOfWood, massOfPlastic, and massOfMetal. The parameterised form states the uniform structure without repeating the declaration.

Derived parameterised attributes

A derived attribute may take parameters. Its parameters are in scope in its defining expression:


value TemperatureGrid
{
    Real celsius(Integer x, Integer y);

    → Real fahrenheit(Integer x, Integer y) =
        celsius(x, y) * 1.8 + 32;
}

For each temperature grid and pair of coordinates, the extension of fahrenheit(x,y) is determined by the extension of celsius(x,y). A derived parameterised attribute cannot be the target of an update statement.

Queries involving parameterised attributes

There is no parsing ambiguity between tuple construction and parameterised attribute application in a path. Tuple construction is introduced by a dot followed immediately by parentheses, whereas a parameterised attribute has its name before the argument list:


s.supplier.(name, city)    // tuple construction
a.f(x, y)                  // parameterised attribute application

Given a path object that reaches Object values, supplying a parameter selects the corresponding predicate:


// Objects whose mass of wood is greater than 10.
object.[mass(Material.wood) > 10]

A query may instead quantify over the parameter:


// Objects having more than 10 units of some material.
object.[∃ material:Material (mass(material) > 10)]

Updating parameterised attributes

Applying arguments to a base parameterised attribute selects an updateable predicate location:


object.mass(Material.wood) := 12;

The update changes only the extension selected by that object and parameter value, subject to the ordinary cardinality and constraint rules.

Large or infinite parameter domains

Exactly-one cardinality over a large or infinite parameter domain is a strong requirement. The ScalarField declaration requires a result for every pair of real numbers. Similarly:


value SampledImage<Type T>
{
    T val(Integer x, Integer y);
}

requires a result for every pair of integers. When only a bounded or sparse set of indices is intended, the declaration should use suitably restricted parameter types or an appropriate non-total cardinality. The requirement is semantic and does not prescribe dense storage, sparse storage, a procedural representation, or any other implementation technique.

Embedding parameterised attributes

An embedding can define a parameterised base attribute pointwise. A constant image can therefore subtype the concrete type SampledImage<Real> while storing only one sample:


value ConstImage
{
    Real sample;
}

embed ConstImage image isa SampledImage<Real> =
    SampledImage<Real> {
        val(x, y) = image.sample
    };

In the proposed constructor syntax, the parameter names in val(x,y) are bound over their declared parameter types. The initializer defines the complete indexed family of predicate extensions without enumerating the integer coordinate pairs.

Through the embedding, val is inherited by ConstImage; it does not need to be declared again. The embedding keeps the compact representation of ConstImage explicit while making it a subtype of the concrete type SampledImage<Real>.