user_guide:extend:polymorphic

This is an old revision of the document!


Polymorphic Functions

In the rulefiles you can define polymorphic functions and methods resembling, to some extent, the features of programming languages C++ or Java. The general syntax is described among other rulefile elements, here we discuss the definition and overload resolution in deeper details.

Let's recall that a polymorphic function is introduced with the following header:

category labels : name < type parameters > [ typecheck ] ( signature ) : attributes

category can be method, function, etc., as described in the general syntax rules.

Name and signature are mandatory, while labels, type parameters, and attributes are optional.

The signature describes the number and types of arguments expected by an overloaded instance, as well as default values for optional arguments. The syntax of the most primitive elements is borrowed from the standard perl function prototypes, enriched with further elements for property types and “big” object types, option lists and other keyword arguments, etc. The “standard” elements are encoded with one-symbol codes, they can be written in traditional style glued together, or in a more legible style with commas and white spaces in between. Advanced elements must be separated with commas.

Signatures of pure perl functions and wrapped C++ functions share many common elements, but also have few differences. Unless mentioned explicitly, the elements described below may be used in both cases. C++ functions are recognized by the c++ attribute.

$

Denotes a single argument of an arbitrary type. Even an undef value would be accepted.

i

Denotes a single argument of simple scalar type (not a reference to an object or to an array or hash map). The signature will match only if the passed argument represents an integral number. This code is not allowed for wrapped C++ functions, use the property type Int instead.

f

The same like above, but additionally matches floating-point numbers. This code is not allowed for wrapped C++ functions, use the property type Float instead.

*

Denotes a single argument of an arbitrary type with a restriction that is must have been declared as a property type with C++ binding. This is a special form for C++ function wrappers. The actual type of the argument passed to the function will be pasted into the auto-generated C++ wrapper code.

typename

Denotes a single argument of the given type. The type must be declared as property type or “big” object type in the rulefiles. For simple types, the signature will match any object of the named type or derived thereof; for parameterized types, any instance or derivation will be matched.

If a parameterized type is used for a wrapped C++ function, the concrete type of the actually passed argument is pasted into the auto-generated wrapper code.

typename < parameter, … >

Denotes a single argument being a concrete instance of a parameterized type. As usual, objects derived from the given instance will match as well.

@

Denotes arbitrarily many (trailing) arguments of any types. This element must be the last one among all positional arguments in the signature; only keyword lists may follow.

%option_list

Refers to an option list defined in the rulefiles before. Trailing argument pairs of the form keyword => value with keywords matching the keys of the option list are collected together in an anonymous hash map, which is passed to the function body by reference.

A signature may contain more than one option list reference. The arguments passed to the function are reorganized to match the keys of each option list; each anonymous hash map is then passed to the function body per separate reference. If the option lists share some keys, the keyword arguments matching several lists are copied in all corresponding hash maps.

If the argument list already contains a reference to a hash map at the position where keyword-value pairs are expected, this reference is passed unchanged and unchecked to the function body; this allows to gradually define families of polymorphic functions delegating work to each other without expensive re-checking and reordering of keyword arguments.

{ key => default value, … }

Like above, but specifying an anonymous option list used just in this single function. Default values different from undef will always be present in the anonymous hash map passed to the function body. This element may be combined with any number of references to named option lists.

%

Specifies that arbitrary keyword-value pairs may be passed to the function. The passed keywords are not checked at all, everything goes into a single anonymous hash map. This element must be the last one in a signature and can't be combined with other option lists, neither named nor anonymous.

Separators are placed between signature elements.

;

Separates the specifications of mandatory positional arguments (to the left) from optional positional arguments (to the right).

|

Combines two type expressions into a list of alternatives; may be repeated to yield lists of any length. This signature element matches an argument belonging to one of the alternative types or derived thereof.

Modifiers are placed after signature elements.

= value
=( expression )

Specifies a default value for an optional positional argument. Complex expressions involving function calls or array/hash map access must be enclosed in parentheses. Optional positional arguments without default value stay undef when called with a too short list of arguments.

Expressions may involve values of preceding arguments by referring to the elements of the array @_. The references may be absolute, using non-negative indexes (remember to count the additional leading argument passed to methods), or relative to the current argument, using negative indexes.

+

Specifies that one or more objects of the given type are expected here. As with @, such an element must be the last one among all positional arguments.

:int

Only applicable to a * element of a C++ function, tells that the expected argument may be either an object of some class or an integral number; floating-point numbers and strings have to be converted to integers before calling the C++ function.

:wary

Only applicable to a * or parameterized type element of a C++ function, tells that the type name in the auto-generated C++ wrapper source code must be adorned with Wary< …> class. This usually results in additional consistency checks performed prior to the operation. Please be aware that not all classes in the Polymake Template Library support this Wary flavor; you should study the implementation of the C++ function before writing this attribute in the wrapper.

:lvalue

Only allowed for C++ functions, tells that the function is going to change the argument, thus is has to be passed by non-const reference. If the data actually passed to the function is write-protected (e.g. it constitutes a property value), an exception will be raised.

:lvalue_opt

Only allowed for C++ functions, tells that there are two flavors of a C++ function, differing in the const-ness of the corresponding attribute. If the data actually passed to the function is write-protected, the generated wrapper source code will contain an argument passed by const reference, otherwise it will be a non-const reference.

:anchor

Only allowed for C++ functions, designates an argument which will be referred to from the result of the function. For example, an object produced by Matrix minor method refers to the row and column index sets. An additional precaution is taken against vanishing of such an argument prior to the destruction of the resulting object.

Almost all of these modifiers are mutually exclusive. The only allowed combinations are wary with anchor and lvalue or lvalue_opt. For obvious reasons, lvalue and lvalue_opt can't be applied to optional arguments.

The type parameter list introduces names for type placeholders which may be used in the signature as well as in the body of the function. If a type parameter occurs in the signature as a type of an argument or as a part of a complex type expression, in each call it will be deduced from the types of the actual arguments passed to the function. If the parameter occurs more than once in the signature and the deduction would lead to different types, the call is aborted and an exception is raised. If a type parameter does not occur in the signature or can't be deduced because of omitted optional argument, and it does not have a default value, it must be specified explicitly in the call using the familiar syntax for function templates.

For example, the function creating a unit matrix of a given size is defined like this:

user_function unit_matrix<Scalar=Rational>($) ...

Then, if called as unit_matrix($dim), the default type Rational will be assumed, and the result will be a Matrix<Rational>, while a call unit_matrix<Integer>($dim) will deliver a Matrix<Integer>.

Actually, explicit values can also be given for deducible type parameters, but the values must exactly match the deduction outcome, otherwise an exception is raised.

In a C++ function, the values of type parameters (regardless whether explicit or deduced) are included in the auto-generated source call as explicit template arguments immediately following the function name. As an exception, type parameters with names starting with an underscore may only be used for type checks and construction of default values of other type parameters; they are not included in the auto-generated C++ code.

In the body of a perl function, the names of the placeholders may be used as normal types in all expressions expecting package or type names, especially in constructor calls new TYPE(…) or type checks like if (instanceof TYPE(…)) { … } .

Due to limitations of the perl parser, methods currently can't be equipped with type parameters.

Trailing attributes are used to modify the semantics of the leading argument of methods, describe the return value of the function, or contribute to the auto-generated wrapper code. All trailing attributes described below are only applicable to C++ functions.

c++( options for source code )

Tells that a function or method is implemented in C++ and a wrapper function must be generated by the first use of it. If the signature contains wildcards like * or parameterized types (or the method belongs to a parameterized type), a separate wrapper instance will be automatically generated for every new combination of argument types.

Options can be used to modify the look of the generated wrapper code:

include => [ "header file", ... ]

Add #include preprocessor directives with the given files to the source code. Further header files might be included depending on the argument types.

name => "function name"

Specify the C++ name of the function, if it differs from the name declared for the perl side.

If no options are needed, the empty parentheses can be omitted.

For those most adventurous among the readers: if you want to see how an automatically generated wrapper code looks like, you will find a lot of examples in the src/perl subdirectories of all applications. The files starting with auto- contain the wrappers generated for functions and methods declared with c++ attributes in the rulefiles.

lvalue, lvalue_opt, wary, anchor

Only allowed for methods, apply the corresponding modifiers to the leading argument (object reference) in the method call. Methods attributed with lvalue and lvalue_opt are expected to return the reference to their object or some part thereof, preserving the const-ness. Therefore anchor is implied by lvalue and lvalue_opt.

non_const

Only allowed for methods, tells that the leading argument has to be passed by a non-const reference.

static

Only allowed for methods, designates a static C++ member function. The package name or object reference used to call the method on the perl side are not passed to the C++ function.

void

Tells that the function does not return any value.

returns( type )

Tells that the function returns an object of the given type. Usually the return value is automatically recognized by the auto-generated wrapper and hooked under an appropriately “blessed” perl reference. This attribute should only be used in special cased where the automatic recognition does not work or must be overridden.

returns( type <*>)

Tells that the return value is to be treated as an instance of the given abstract property type.

returns(@)

Tells that the function returns several values in a list context. As usual in perl, when called in scalar context, the last value from the list will be taken for further processing.

When a polymorphic function is called, the first action is always to determine the instance best matching the given list of arguments. This is also often called overload resolution.

The first rough filtering is based on the number of arguments passed to the function. Then the set of candidates is gradually narrowed by analyzing the types of the arguments from left to right. For polymorphic methods, the first argument has already been used for locating the method as such, hence it does not participate in the further resolution.

The signature matching is done in greedy manner. In each round only those candidate instances “survive” whose signature elements most closely match the corresponding argument. This means, an exact match of the type (or one type from a list of alternatives) is preferred over match against a derived type, which in its turn is valued higher than a match against a universal element like '$' or '*'. Only if the candidate set becomes empty in some round, the resolver makes a step back and considers the next closest candidates.

The trailing keyword-value pairs are not taken into account apart from their mere presence; the search for keys in the option lists takes place after the unique matching instance is found.

It is an error if the set of candidates after having processed the entire argument list does not consist of exactly one instance. The fact that some argument list may equally good match more than one feasible candidate is recognized already when the rulefiles are parsed. Not having matched any instance at all gives rise to an exception.

Please keep in mind that two signatures differing in optional and keyword arguments alone would lead to ambiguous overloading each time when only mandatory arguments are passed, and therefore will be rejected by rulefile parser. For example, the following pairs of signatures are illegal:

  • ($$) and ($$;$=0)
  • ($$;Class_A) and ($$;Class_B)
  • ($$) and ($$;%my_options)

Type parameters, if any, does not influence the overload resolution. The type deduction is performed after the resolution has determined a unique matching function instance. Errors emerging in the course of type deduction cause the function call to be aborted, no attempts are made to find another candidate instance.

Usually a function is either implemented in pure (polymake dialect of) perl or wrapped from the C++ side. In the first case the instance body with the code is directly following the header, like in a standard perl subroutine, in the second case the list of attributes is concluded by a semicolon.

Sometimes you might find more convenient to combine some preliminary checks or preparations on argument values with a proper call into C++ function, or even skip the latter call under certain circumstances. Although these scenarios can surely be modeled by defining two functions, outer one written in perl, and the inner one as wrapped C++ function, this also can be written more compactly as a hybrid function. Such a function has both the c++ attribute and a body with perl code. The body gets an additional trailing argument pointing to the wrapped C++ function. Usually a hybrid function looks like this:

function NAME( signature ) : c++ {
  if (some_checks(...)) {
    # call the C++ function
    &{pop(@_)}
  } else {
    # do something else
  }
}

Labels allow to differentiate between several overloaded instances with identical signatures. Without labels, occurrence of functions with compatible signatures would inevitably give rise to a fatal error. (Recall that two signatures are compatible if they would match the same argument list shortened by all optional arguments). With labels, the instances with compatible signatures are bundled together in lists, which are consulted at the end of the overload resolution. The actual function body to be executed is taken from the head of the candidate list. If one of the labels is made preferred by executing a command prefer or prefer_now, the corresponding instance is moved to the head of the list. Otherwise the order of the instances is more or less random, thus it is unpredictable which instance is called in absence of any active preferences.

The application can also obtain the complete candidate list, ordered according to currently active preferences, by executing $list=Overload::resolve_labeled("PACKAGE", "NAME", args); The package name to search within can be replaced with a special token __PACKAGE__ designating the current package.

While assigning labels to functions is optional, for one category of polymorphic functions labels are mandatory, namely for global methods. The overload resolution for global methods runs slightly differently from the normal methods. The application code first calls $method=Overload::Global::NAME(args) to obtain the code reference pointing to the currently preferred method matching the given list of arguments. Then it creates an object of the corresponding class using something like $object=method_owner($method)->new(...); or retrieves a suitable object in whatever appropriate way, and finally calls the method, passing the obtained object as a leading argument: $method->($object, args);

Note that the method selected in the first step is looked for in all packages defining global method instances with the given name.

  • user_guide/extend/polymorphic.1383780355.txt.gz
  • Last modified: 2014/01/03 15:45
  • (external edit)