-
Notifications
You must be signed in to change notification settings - Fork 6
Clone types
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. This allows you to do fancy math things like this:
newtype
Vec = clone list
def
(v Vec) + (w Vec) :
Vec from z = [] for i::x = range v :
z + [x + w[i]]
Mostly however we anticipate that this feature will be used simply to distinguish inert pieces of data on which we don't want to perform operations: a username, a SKU, a web address, each of which might be distinguished as a different clone of the string
type.
🧿 Pipefish is distributed under the MIT license. Please steal my code and ideas.