Like any other programming languages Morphir has a type system as well. This module defines the building blocks of that type system. If you want to learn more about type systems check out Wikipedia: Type system.
Morphir's type system is heavily inspired by Elm's type system so the best way to understand the building blocks here is through some Elm examples. Let's take this bit of Elm code as a starting point:
type alias MyInteger =
Int
type alias MyRecord a =
{ foo : List a
}
type Foo a
= Bar a
| Baz
These would translate to type definitions in Morphir which is represented by the Definition
type.
Definitions themselves don't have a name. It's the Module that contains that information in the types
dictionary as a
key. The type parameters and the right-hand side of the declaration is contained in the Definition
type
itself. This is how the above would translate to the IR (some parts are omitted to reduce noise):
{ types =
Dict.fromList
[ ( [ "my", "integer" ], TypeAliasDefinition [] (...) )
, ( [ "my", "record" ], TypeAliasDefinition [ [ "a" ] ] (...) )
, ( [ "foo" ], CustomTypeDefinition [ [ "a" ], [ "b" ] ] (...) )
]
, values =
Dict.empty
}
Type aliases simply assign a new name to a type expression. This type expression can be a reference to another type or a record type or any other type expression. Custom types are defined by a list of constructors. Each of those constructors have a list of arguments. Each argument is a type expression.
Here is the full definition for reference:
{ types =
Dict.fromList
[ ( [ "my", "integer" ]
, TypeAliasDefinition []
(Reference (fqn "Morphir.SDK" "Basics" "Int") [])
)
, ( [ "my", "record" ]
, TypeAliasDefinition [ [ "a" ] ]
(Record ()
[ Field [ "foo" ] (Reference () (fqn "Morphir.SDK" "List" "List") [ Variable () [ "a" ] ])
]
)
)
, ( [ "foo" ]
, CustomTypeDefinition [ [ "a" ], [ "b" ] ]
(AccessControlled.public
[ Constructor [ "bar" ] [ ( [ "arg", "1" ], Variable () [ "a" ] ) ]
, Constructor [ "baz" ] [ ( [ "arg", "1" ], Variable () [ "b" ] ) ]
]
)
)
]
, values =
Dict.empty
}
Represents a type expression that can appear in various places within the IR. It can be the right-hand-side of a type alias declaration, input and output types of a function or as an annotation on values after type inference is done.
Type are modeled as expression trees: a recursive data structure with various node types. The type argument a
allows
us to assign some additional attributes to each node in the tree. Here are some details on each node type in the tree:
Unit
.a -> b -> c
is represented as a -> (b -> c)
variable : a -> Morphir.IR.Name.Name -> Type a
Creates a type variable.
toIR a == variable [ "a" ] ()
toIR fooBar == variable [ "foo", "bar" ] ()
reference : a -> Morphir.IR.FQName.FQName -> List (Type a) -> Type a
Creates a fully-qualified reference to a type.
toIR (List Int)
== reference SDK.List.listType [ intType ]
toIR Foo.Bar
== reference
( [ [ "my" ], [ "lib" ] ], [ [ "foo" ] ], [ "bar" ] )
[]
tuple : a -> List (Type a) -> Type a
Creates a tuple type.
toIR ( Int, Bool )
== tuple [ basic intType, basic boolType ]
record : a -> List (Field a) -> Type a
Creates a record type.
toIR {} == record []
toIR { foo = Int }
== record
[ field [ "foo" ] SDK.Basics.intType
]
toIR { foo = Int, bar = Bool }
== record
[ field [ "foo" ] SDK.Basics.intType
, field [ "bar" ] SDK.Basics.boolType
]
extensibleRecord : a -> Morphir.IR.Name.Name -> List (Field a) -> Type a
Creates an extensible record type.
toIR { e | foo = Int }
== extensibleRecord (variable [ "e" ])
[ field [ "foo" ] intType
]
toIR { f | foo = Int, bar = Bool }
== extensibleRecord (variable [ "f" ])
[ field [ "foo" ] intType
, field [ "bar" ] boolType
]
function : a -> Type a -> Type a -> Type a
Creates a function type. Use currying to create functions with more than one argument.
toIR (Int -> Bool) ==
function
SDK.Basics.intType
SDK.Basics.boolType
toIR (Int -> Bool -> Char) ==
function
intType
(function
SDK.Basics.boolType
SDK.Basics.charType
)
unit : a -> Type a
Creates a unit type.
toIR () == unit
{ name : Morphir.IR.Name.Name
, tpe : Type a
}
An opaque representation of a field. It's made up of a name and a type.
mapFieldName : (Morphir.IR.Name.Name -> Morphir.IR.Name.Name) -> Field a -> Field a
Map the name of the field to get a new field.
mapFieldType : (Type a -> Type b) -> Field a -> Field b
Map the type of the field to get a new field.
Represents the specification (in other words the interface) of a type. There are 4 different shapes:
type alias Foo = String
type Foo = Bar | Baz Int
LocalDate
may have different representations on various platforms but we can define a standard
way to map from and to a string.The first List Name
argument represents type parameters in each variant. For example type alias Foo a b = ...
would map to TypeAliasSpecification [ ["a"], ["b"] ] ...
.
typeAliasSpecification : List Morphir.IR.Name.Name -> Type a -> Specification a
opaqueTypeSpecification : List Morphir.IR.Name.Name -> Specification a
customTypeSpecification : List Morphir.IR.Name.Name -> Constructors a -> Specification a
{ baseType : Type a
, fromBaseType : Morphir.IR.FQName.FQName
, toBaseType : Morphir.IR.FQName.FQName
}
Details of the base type of a Derived Type
This syntax represents a type definition. For example:
type alias Foo a = {bar : Maybe a, qux : Int}
type MyList a = End | Cons a (MyList a)
In the definition, the List Name
refers to type parameters on the LHS
and Type extra
refers to the RHS
typeAliasDefinition : List Morphir.IR.Name.Name -> Type a -> Definition a
customTypeDefinition : List Morphir.IR.Name.Name -> Morphir.IR.AccessControlled.AccessControlled (Constructors a) -> Definition a
definitionToSpecification : Definition a -> Specification a
definitionToSpecificationWithPrivate : Definition a -> Specification a
Dict Morphir.IR.Name.Name (ConstructorArgs a)
Constructors in a dictionary keyed by their name. The values are the argument types for each constructor.
( Morphir.IR.Name.Name
, ConstructorArgs a
)
Represents a single constructor with a name and arguments.
List ( Morphir.IR.Name.Name
, Type a
}
Represents a list of constructor arguments.
mapTypeAttributes : (a -> b) -> Type a -> Type b
mapSpecificationAttributes : (a -> b) -> Specification a -> Specification b
mapDefinitionAttributes : (a -> b) -> Definition a -> Definition b
mapDefinition : (Type a -> Result e (Type b)) -> Definition a -> Result (List e) (Definition b)
typeAttributes : Type a -> a
eraseAttributes : Definition a -> Definition ()
collectVariables : Type ta -> Set Morphir.IR.Name.Name
Collect all variables in a type recursively.
collectReferences : Type ta -> Set Morphir.IR.FQName.FQName
Collect all references in a type recursively.
collectReferencesFromDefintion : Definition ta -> Set Morphir.IR.FQName.FQName
Collect references from a Type Definition
substituteTypeVariables : Dict Morphir.IR.Name.Name (Type ta) -> Type ta -> Type ta
Substitute type variables recursively.
toString : Type a -> String
Get a compact string representation of the type.