W Wrapl, The Programming Language

Chapter 6

Types

Lists

We have seen how the expression ALL expr returns a list of all the values produced by expr. Lists in Wrapl can contain any number of elements, each of which can have any type including other lists or compound types. Lists can be written between square brackets [] and separated by commas.

--> [1, 2, 3, 4]; [1, 2, 3, 4] --> [0, "a", [9]]; [0, "a", [9]]

Lists are indexed using [], the first element having index 1 with the last having index list:length. Like strings, lists can be indexed with negative indices, the first element having index -list:length with the last having index -1. Unlike strings, indexing a list returns an assignable reference.

Wrapl supports object-oriented programming (OOP) and provides a class-based object model; i.e. there are classes, called types in Wrapl, and instances of those classes, called objects. Every value in Wrapl is an object and therefore is an instance of some type. Types may inherit from any number of other types (multiple inheritance) and in turn may be extended by any number of subtypes.

Wrapl provides a number of predefined types, some of which are listed in the table below.
TypeDescriptionRepresentation
Std.Object.TThe base type of all other objects.NIL is the only notable instance.
Std.Type.TThe type of all types.<[parent, ...]field, ...>.
Std.Integer.TInteger values of unlimited size.Any number of digits.
Std.Real.TLimited (64bit) precision floating point numbers.Any number of digits containing a decimal point and/or exponent.
Std.String.TImmutable strings.Characters enclosed in quotes, "". May contain escaped control characters.
Std.Function.TThe base type of all functions.<parameters> expression.
Std.Symbol.TMultiple dispatch functions.:identifier or :"string".
Agg.List.TMutable lists of objects.[expr,...].
Agg.Table.TKey/Value maps.{expr IS expr, ...}.

New types can be created in Wrapl using the syntax <[parent, ...]field, ...>. A new instance of a type can be created by calling the type like a function.

Each field should be an identifier or a symbol. This is because fields in an object are accessed by calling a symbol with the object as the first parameter. If the field is supplied as an identifier, then the symbol with that name is used to access the field, however if a symbol is supplied directly, then that symbol is used. Note that the expression t:x is equivalent to :x(t). For example:

1DEF T <- <[] x, :":)">; 2 3VAR t <- T(); 4t:x <- 100; 5t:":)" <- 200;

Each parent should evaluate to a type. Any number of parents, including none, is allowed. All types implicitly inherit from Std.Object.T. A type will inherit all fields from each of its parents. If a same field is defined in more than one parent, then only one instance of the field will be allocated. Hence multiple inheritance should be used carefully (as in any language which supports it).

1DEF T <- <[] x>; 2DEF U <- <[T] y>; 3 4VAR u <- U(); 5u:x <- 100; 6u:y <- 200;

Multiple Dispatch

Being object-oriented, Wrapl provides methods; functions whose behaviour depend on the types of their arguments. In most OOP languages, methods depend only on the type of the first argument (which is usually written preceding the rest of the function call). However in Wrapl, methods depend on the types and values of all arguments.

Methods in Wrapl are stored in symbols; a colon : followed immediately by either an identifier or a string. For example, :x, :write and :"*" are all symbols. A symbol represented by an identifier is the same as the symbol represented by the equivalent string. Thus :write and :"write" represent the same symbol. In addition, any symbols represented by the same string are the same. Symbols are created implicitly when they are first used.

Each symbol has a set of signatures and corresponding functions, where each signature is a list of types or values. When a symbol is called with arguments, the types and values of the arguments are checked against the various signatures and the function corresponding to the closest matching signature is called with the same arguments. If no signature matches the call, then a Std.Symbol.NoMethodMessageT message is sent. When calling a symbol, the first argument can be placed before the symbol for convenience. For example: t:x is equivalent to :x(t) and t:m(a, b, c) is equivalent to :m(t, a, b, c).

To add a new signature and corresponding function, the syntax TO symbol(signature) IS function is used. Here a signature looks like a list of parameters, each of which can be followed by either @ type or = value. If @ type is present then the signature matches a call if the type of the corresponding argument is either type or a subtype of type. If = value is present then the signature matches a call if the corresponding argument is the same object as value (not just equal in value). If neither is present, then the signature matches a call if any argument is supplied for that parameter. The actual names of the parameters are ignored.

The syntax TO symbol(signature) expression is shorthand for TO symbol(signature) IS <parameters> expression where the parameter names are taken from the signature.

Note that TO expressions are translated into calls to Std.Symbol.Set. Hence they may occur in other expressions, for example within a conditional expression. Also, the signature and corresponding function will not work until after the call to Std.Symbol.Set has returned, allowing signature/function pairs to be temporarily overridden, or even prevent them being overridden by calling the symbol with the correct arguments and checking for a Std.Symbol.NoMethodMessageT message.

Example:
1DEF T <- <[]>; 2DEF U <- <[T]>; 3 4TO :a(t@T) Out:write(":a(t@T)"); 5TO :a(u@U) Out:write(":a(u@U)"); 6TO :b(t@T) Out:write(":b(t@T)"): 7 8VAR t <- T(), u <- U(); 9 10t:a; -- writes ":a(t@T)" 11t:b; -- writes ":b(t@T)" 12u:a; -- writes ":a(u@U)" 13u:b; -- writes ":b(t@T)" 14t:c; -- sends a Symbol.NoMethodMessageT message

Navigate