-
Notifications
You must be signed in to change notification settings - Fork 5
Abstract types
In this page we will review the concept of "abstract types".
The essential point to remember is this. A concrete type is something a Pipefish value can have. An abstract type is a filter on what a variable can contain, or the parameter of a function, or the field of a struct.
So if for example x
is a variable of type int?
, this means that it can contain a value of type int
or of type null
. It cannot contain a value of type int?
, because there are no values of type int?
.
As the previous example suggests, every one of the types we've met so far has an abstract "nullable twin", written <typename>?
, which can contain either values of the type typename
or the value NULL
of type null
: so for example int?
contains either something of type int
or it contains NULL
. Of course this works for user-defined types too.
Note that you can't compare a non-null value with NULL
, because you can't compare values of different types. So if you want to know if a nullable parameter/variable/field foo
is equal to NULL
, you can't write foo == NULL
(because that would fail at runtime if foo
wasn't NULL
) but instead must use the conditional expression foo in null
to test its type membership.
The abstract type any
contains all types that aren't null
or tuple
. struct
contains all structs, enum
contains all enum values, and label
contains all field labels of structs. Each of them has a nullable twin, e.g. any?
contains everything except tuple
.
We will meet a few more built-in abstract types on the next page.
You can define new abstract subtypes in the newtype
section.
newtype
Number = int/float
While concrete types are nominal, abstract types are structural: an abstract type really is just a set of concrete types, and any two abstract types with the same underlying set are the same.
Abstract types can be constructed "on the fly", e.g. 42 in int/float
is a valid expression. On the other hand abstract types must be defined in the newtype
section before they can be used in function signatures, rather than you being able to define foo(x int/string)
: this will be changed in a future version but is not a high priority.
The abstract types above are supertypes of concrete types. varchar
by contrast supplies us with a family of abstract subtypes of strings: varchar(42)
is a type containing strings with 42 or fewer characters. This is an anomaly tacked on to the type system for the purposes of SQL interop. You can't make your own abstract subtypes because they shouldn't really exist.
In functions and struct definitions, you can just use an abstract type as you would use a concrete one.
newtype
Penguin = struct(name string, numberOfChicks int?, favoriteFood string?)
def
nullToZero(i int?) :
i in null : 0
else : i
To widen or narrow a variable type you put the name of the concrete type after the name of the variable when declaring it. Note that this syntax is just like function / struct declarations: the syntax is <name> <type>
.
var
couldBeInt int? = 42
couldBeBool bool? = NULL
shortString varchar(10) = "walrus"
If you don't do that, then the variable is automatically given the concrete type of the thing assigned to it. So for example if above we had just written couldBeBool = NULL
, then the variable couldBeBool
could not in fact be bool
: it would be given the type null
and could never contain anything but NULL
.
🧿 Pipefish is distributed under the MIT license. Please steal my code and ideas.