Advanced types
Some types must be explicitly defined and given a name before they may be used.
Structures
A structure is like a tuple with named values.
It is similar to a struct or class in other languages.
Unlike primitive types, identical structures with different names not the same type.
Structures are defined using type
statements:
<span class='hljs-keyword'>type</span> Vector2 = <span class='hljs-punctuation'>{</span> x <span class='hljs-punctuation'>:</span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-type'>real</span><span class='hljs-punctuation'>,</span> y <span class='hljs-punctuation'>:</span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-type'>real</span> <span class='hljs-punctuation'>}</span>
<span class='hljs-keyword'>let</span> origin <span class='hljs-punctuation'>:</span> <span class='hljs-type'>Vector2</span> = <span class='hljs-punctuation'>{</span>
x = <span class='hljs-variable constant_'>0.0</span><span class='hljs-punctuation'>,</span>
y = <span class='hljs-variable constant_'>0.0</span><span class='hljs-punctuation'>,</span>
<span class='hljs-punctuation'>}</span>
<span class='hljs-keyword'>let</span> x = origin.x
<span class='hljs-keyword'>let</span> y = origin.y
In rare cases, type inference for structures may fail. This is because some structures may be exactly the same except for their name. For this reason, it is best practice to write type annotations for structures.
Sum types
Sum types are similar to enums in other languages.
<span class='hljs-keyword'>type</span> Class = <span class='hljs-title function_'>Knight</span> | <span class='hljs-title function_'>Mage</span> | <span class='hljs-title function_'>Rogue</span>
<span class='hljs-keyword'>let</span> class_greeting = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>class</span> <span class='hljs-keyword'>=></span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>println</span>
<span class='hljs-punctuation'>(</span><span class='hljs-keyword'>match</span> class <span class='hljs-keyword'>with</span>
| <span class='hljs-title function_'>Knight</span> <span class='hljs-keyword'>=></span> <span class='hljs-string'>"I am a knight"</span>
| <span class='hljs-title function_'>Mage</span> <span class='hljs-keyword'>=></span> <span class='hljs-string'>"I am a mage"</span>
| <span class='hljs-title function_'>Rogue</span> <span class='hljs-keyword'>=></span> <span class='hljs-string'>"I am a rogue"</span><span class='hljs-punctuation'>)</span>
<span class='hljs-keyword'>do</span>
<span class='hljs-title function_'>class_greeting</span> Knight<span class='hljs-punctuation'>;</span>
<span class='hljs-title function_'>class_greeting</span> Mage<span class='hljs-punctuation'>;</span>
<span class='hljs-title function_'>class_greeting</span> Rogue
A sum type may contain data attached to one of its variants.
Imagine you are writing a function safe_divide
that checks if the denominator is zero.
Using sum types, we can show that this function may fail to return a number:
<span class='hljs-keyword'>type</span> Result = <span class='hljs-title function_'>Ok</span> <span class='hljs-keyword'>of</span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-type'>real</span> | <span class='hljs-title function_'>DivisionError</span>
<span class='hljs-keyword'>let</span> safe_divide = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>numerator</span> <span class='hljs-params'>denominator</span> <span class='hljs-keyword'>=></span>
<span class='hljs-keyword'>match</span> denominator <span class='hljs-keyword'>with</span>
| <span class='hljs-variable constant_'>0</span> <span class='hljs-keyword'>=></span> DivisionError
| _ <span class='hljs-keyword'>=></span> <span class='hljs-title function_'>Ok</span> <span class='hljs-punctuation'>(</span>numerator <span class='hljs-operator'>/.</span> denominator<span class='hljs-punctuation'>)</span>
The Result
type defines two constructors, Ok
and DivisionError
.
Because DivisionError
contains no data, it is actually just a constant with the type Result
.
The Ok
constructor does contain data, so it is a function with the type real -> Result
.
Sum types are extremely flexible; Halcyon uses them as a substitute for null pointers, exceptions, and sub-types.
A result type already exists in the standard library result
module, including lots of useful functions for working with results.
See also: the opt
module
Implicit Polymorphism
We discussed earlier that Halcyon has full type inference. What do you think the type of this function will be inferred to be?
<span class='hljs-keyword'>let</span> identity <span class='hljs-comment'>(* ? -> ? *)</span> = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>a</span> <span class='hljs-keyword'>=></span> a
The function identity
simply returns whatever its parameter is.
There are no context clues forcing a
to be any type in particular, and so identity
is implicitly polymorphic.
In simple terms, this means that its argument may be any type:
<span class='hljs-keyword'>do</span>
<span class='hljs-title function_'>identity</span> <span class='hljs-variable constant_'>1</span><span class='hljs-punctuation'>;</span>
<span class='hljs-title function_'>identity</span> <span class='hljs-built_in'>false</span><span class='hljs-punctuation'>;</span>
<span class='hljs-title function_'>identity</span> <span class='hljs-string'>"foo"</span>
The exact type of identity
is '0 -> '0
, where '0
is a type variable.
Type variables are a placeholder type that gets replaced when a function is actually called.
A functions type may have any number of type variables.
<span class='hljs-keyword'>let</span> first = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>a</span> <span class='hljs-params'>b</span> <span class='hljs-keyword'>=></span> a
<span class='hljs-keyword'>let</span> second = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>a</span> <span class='hljs-params'>b</span> <span class='hljs-keyword'>=></span> b
<span class='hljs-keyword'>do</span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>println</span> <span class='hljs-punctuation'>(</span><span class='hljs-title function_'>first</span> <span class='hljs-string'>"foo"</span> <span class='hljs-variable constant_'>2</span><span class='hljs-punctuation'>)</span>
<span class='hljs-keyword'>do</span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>println</span> <span class='hljs-punctuation'>(</span><span class='hljs-title function_'>second</span> <span class='hljs-built_in'>false</span> <span class='hljs-string'>"bar"</span><span class='hljs-punctuation'>)</span>
Here, first
has the type '0 -> '1 -> '0
, while second
has the type '0 -> '1 -> '1
.
Explicit Polymorphism
Types can contain type variables just like functions. Polymorphic types are called type functions, and follow slightly different rules from regular types.
The standard library defines the opt
type, similar to the Result
type above except that it can contain any type, not just real
.
Let's see how it is implemented:
<span class='hljs-keyword'>module</span> opt =
<span class='hljs-keyword'>type</span> t = <span class='hljs-keyword'>fn</span> <span class='hljs-type'>a</span> <span class='hljs-keyword'>=></span> <span class='hljs-title function_'>Some</span> <span class='hljs-keyword'>of</span> <span class='hljs-type'>a</span> | <span class='hljs-title function_'>None</span>
<span class='hljs-keyword'>end</span>
Here, opt
, is not a type per se, but rather a function that returns a type.
The parameters to a type function (in this case a
) are other types.
When you use a polymorphic type, the compiler will infer the correct type parameter for you.
<span class='hljs-keyword'>let</span> safe_divide = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>numerator</span> <span class='hljs-params'>denominator</span> <span class='hljs-keyword'>=></span>
<span class='hljs-keyword'>match</span> denominator <span class='hljs-keyword'>with</span>
| <span class='hljs-variable constant_'>0</span> <span class='hljs-keyword'>=></span> <span class='hljs-title class_'>opt</span><span class='hljs-punctuation'>::</span>None
| _ <span class='hljs-keyword'>=></span> <span class='hljs-title class_'>opt</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>Some</span> <span class='hljs-punctuation'>(</span>numerator <span class='hljs-operator'>/</span> denominator<span class='hljs-punctuation'>)</span>
<span class='hljs-comment'>(* safe_divide : integer -> (opt::t integer) *)</span>