Skip to content

Clone types

tim-hardcastle edited this page Jun 8, 2025 · 4 revisions

Besides the other user-defined types, it can be useful to have concrete types which resemble, but are distinct from, the built-in types. If, for example, we use an integer to represent a UID, then it is convenient to declare a UID type which can be distinguished from other integers.

newtype UID = clone int

apples = clone int
oranges = clone int

Fruit = abstract apples/oranges

Cloneable types are float, int, list, map, pair, set, and string. Every clone, together with its parent, belongs to an abstract type called <base type>like, e.g. intlike, maplike.

The elements of the type are constructed as you would expect: Uid 5; the int and float types are also supplied with suffix constructors so you can write e.g. 5 apples. To have it styled like that, simply overload the builtin string function:

string(x fruit) :
	string(x) + " " + string(type x)

It is acceptable to use lowercase for clone types used in this way as units.

We can convert back to the parent type by using its name as a conversion function: int(5 apples) will return 5.

Other converters should be added by hand as needed, e.g. apples "5" or apples(5 oranges) will fail.

The cast function will cast any clone to its parent, parent to a child, or clone to another clone with the same parent: cast 5 apples, int returns 5.

Such clones often need not have the full range of built-in functions and operators, and if they need not, they should not. You would never want to add two UIDs together, let alone multiply them. For this reason, by default the clone types are supplied without certain built-in operations, which can however be had by request:

newtype

apples = clone int using +, -
oranges = clone int using +, -

fruit = abstract apples/oranges

We can now add and subtract apples and apples; and oranges and oranges. Though not, of course, apples and oranges.

Some operations, such as len, don't need to be requested in this way; others, such as +, do. The underlying rule is that an operation must be requested if it would be expected to return a value in the clone type. Hence the operations that need requesting are +, -, *, /, %,with, without, the operators >> and ?> for lists, and slicing clones of strings and lists. (Request the slicing operation with the word slice, the other operations by their names and symbols.)

An operation which you don't request may of course still be overloaded by hand.

For each type you can clone (e.g. int) there is a corresponding abstract types (e.g. clones{int}) which contains the parent type and everything cloned from it.

This is the first appearance of the {} symbols in this manual. We'll talk about them more later on, but for now the point of them is that they give us (limited) facilities to write an expression which returns a type and which can be used in signatures. E.g. you can define a function with signature foo(x clones{int/float}) -> clones{int/float}; whereas you can't put ordinary Pipefish expressions in function signatures, for reasons of sanity.

Clone this wiki locally