finos / morphir-elm / Morphir.IR.Type

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
}

Type Expression


type Type a
    = Variable a Morphir.IR.Name.Name
    | Reference a Morphir.IR.FQName.FQName (List (Type a))
    | Tuple a (List (Type a))
    | Record a (List (Field a))
    | ExtensibleRecord a Morphir.IR.Name.Name (List (Field a))
    | Function a (Type a) (Type a)
    | Unit a

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:

Creation

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

Record Field


type alias Field a =
{ 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.

Specification


type Specification a
    = TypeAliasSpecification (List Morphir.IR.Name.Name) (Type a)
    | OpaqueTypeSpecification (List Morphir.IR.Name.Name)
    | CustomTypeSpecification (List Morphir.IR.Name.Name) (Constructors a)
    | DerivedTypeSpecification (List Morphir.IR.Name.Name) (DerivedTypeSpecificationDetails a)

Represents the specification (in other words the interface) of a type. There are 4 different shapes:

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


type alias DerivedTypeSpecificationDetails a =
{ baseType : Type a
, fromBaseType : Morphir.IR.FQName.FQName
, toBaseType : Morphir.IR.FQName.FQName 
}

Details of the base type of a Derived Type

Definition


type Definition a
    = TypeAliasDefinition (List Morphir.IR.Name.Name) (Type a)
    | CustomTypeDefinition (List Morphir.IR.Name.Name) (Morphir.IR.AccessControlled.AccessControlled (Constructors a))

This syntax represents a type definition. For example:

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

Constructors


type alias Constructors 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.


type alias Constructor a =
( Morphir.IR.Name.Name
, ConstructorArgs a 
)

Represents a single constructor with a name and arguments.


type alias ConstructorArgs a =
List ( Morphir.IR.Name.Name
, Type a 
}

Represents a list of constructor arguments.

Utilities

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.