finos/morphir-elm/7.0.0

NOTE: the latest version is 22.0.2

Formerly Morgan-Stanley/morphir-elm. Search finds this package by either name.

Morphir.IR.Value

In functional programming data and logic are treated the same way and we refer to both as values. This module provides the building blocks for those values (data and logic) in the Morphir IR.

If you use Elm as your frontend language for Morphir then you should think about all the logic and constant values that you can put in the body of a function. Here are a few examples:

myThreshold =
    1000


min a b =
    if a < b then
        a


    else
        b


addTwo a =
    a + 2

All the above are values: the first one is just data, the second one is logic and the last one has both logic and data. In either case each value is represented by a Value expression. This is a recursive data structure with various node types representing each possible language construct. You can check out the documentation for values below to find more details. Here are the Morphir IR snippets for the above values as a quick reference:

myThreshold =
    Literal () (IntLiteral 1000)


min a b =
    IfThenElse ()
        (Apply ()
            (Apply ()
                (Reference () (fqn "Morphir.SDK" "Basics" "lessThan"))
                (Variable () [ "a" ])
            )
            (Variable () [ "b" ])
        )
        (Variable () [ "a" ])
        (Variable () [ "b" ])


addTwo a =
    Apply ()
        (Apply ()
            (Reference () (fqn "Morphir.SDK" "Basics" "add"))
            (Variable () [ "a" ])
        )
        (Literal () (IntLiteral 2))

Value

Value is the top level building block for data and logic. See the constructor functions below for details on each node type.

type Value ta va
= Literal va Literal
| Constructor va FQName
| Tuple va (List (Value ta va))
| List va (List (Value ta va))
| Record va (List ( Name, Value ta va ))
| Variable va Name
| Reference va FQName
| Field va (Value ta va) Name
| FieldFunction va Name
| Apply va (Value ta va) (Value ta va)
| Lambda va (Pattern va) (Value ta va)
| LetDefinition va Name (Definition ta va) (Value ta va)
| LetRecursion va (Dict Name (Definition ta va)) (Value ta va)
| Destructure va (Pattern va) (Value ta va) (Value ta va)
| IfThenElse va (Value ta va) (Value ta va) (Value ta va)
| PatternMatch
va
(Value ta va)
(List ( Pattern va, Value ta va ))
| UpdateRecord va (Value ta va) (List ( Name, Value ta va ))
| Unit va

Type that represents a value expression. This is a recursive data structure with various node types representing each possible language construct.

The extra type parameters ta and va allow you to add type and value attributes. Type attributes allow you to add extra information to each type node. Value attributes do the same for value nodes. In many cases you might not need this in which case you can just put a unit (()) type or a type variable as a placeholder.

These are the supported node types:

  • Literal
    • Represents a literal value like 13, True or "foo".
    • See the documentation in the Literal module for details on the supported literal types.
    • See [Wikipedia: Literal](https://en.wikipedia.org/wiki/Literal_(computer_programming)) for more details on literals.
  • Constructor
    • Reference to a custom type constructor name.
    • If the type constructor has arguments this node will be wrapped into some Apply nodes depending on the number of arguments.
  • Tuple
    • Represents a tuple value.
    • Each element of the tuple is in turn a Value.
  • List
    • Represents a list of values.
    • Each item of the list is in turn a Value.
  • Record
    • Represents a record value.
    • Each field value of the record is in turn a Value.
  • Variable
    • Reference to a variable.
  • Reference
    • Reference to another value within or outside the module.
    • References are always full-qualified to make resolution easier.
  • Field
    • Represents accessing a field on a record together with the target expression.
    • This is done using the dot notation in Elm: foo.bar
  • FieldFunction
    • Represents accessing a field on a record without the target expression.
    • This is a shortcut to refer to the function that extracts the field from the input.
    • This is done using the dot notation in Elm without a target expression: .bar
  • Apply
    • Represents a function application.
    • The two arguments are the target function and the argument.
    • Multi-argument invocations are expressed by wrapping multiple Apply nodes in each other (currying).
  • Lambda
    • Represents a lambda abstraction.
    • The first argument is a pattern to match on the input, the second is the lambda expression's body.
  • LetDefinition
    • Represents a single let binding.
    • Multiple let bindings are achieved through wrapping multiple let expressions into each other.
  • LetRecursion
    • Special let binding that allows mutual recursion between the bindings.
    • This is necessary because LetDefinition will not make recursion possible due to its scoping rules.
  • Destructure
    • Applies a pattern match to the first expression and passes any extracted variables to the second expression.
    • This can be represented as a let expression with a pattern binding or a single-case pattern-match in Elm.
  • IfThenElse
    • Represents a simple if/then/else expression.
    • The 3 arguments are: the condition, the then branch and the else branch.
  • PatternMatch
    • Represents a pattern-match.
  • UpdateRecord
    • Expression to update one or more fields of a record.
    • As usual in FP this is a copy-on-update so no mutation is happening.
  • Unit
    • Represents the single value in the Unit type.
    • When you find Unit in the IR it usually means: "There's nothing useful here".
literal : va -> Literal -> Value ta va

A literal represents a fixed value in the IR. We only allow values of basic types: bool, char, string, int, float.

True -- Literal (BoolLiteral True)


'a' -- Literal (CharLiteral 'a')


"foo" -- Literal (StringLiteral "foo")


13 -- Literal (IntLiteral 13)


15.4 -- Literal (FloatLiteral 15.4)
constructor : va -> FQName -> Value ta va

A reference to a constructor of a custom type.

Nothing -- Constructor ( ..., [ [ "maybe" ] ], [ "nothing" ] )


Foo.Bar -- Constructor ( ..., [ [ "foo" ] ], [ "bar" ] )
apply : va -> Value ta va -> Value ta va -> Value ta va

Represents a function invocation. We use currying to represent function invocations with multiple arguments.

Note: Operators are mapped to well-known function names.

not True -- Apply (Reference ( ..., [ [ "basics" ] ], [ "not" ])) (Literal (BoolLiteral True))


True || False -- Apply (Apply (Reference ( ..., [ [ "basics" ] ], [ "and" ]))) (Literal (BoolLiteral True)) (Literal (BoolLiteral True))
field : va -> Value ta va -> Name -> Value ta va

Extracts the value of a record's field.

a.foo -- Field (Variable [ "a" ]) [ "foo" ]
fieldFunction : va -> Name -> Value ta va

Represents a function that extract a field from a record value passed to it.

.foo -- FieldFunction [ "foo" ]
lambda : va -> Pattern va -> Value ta va -> Value ta va

Represents a lambda abstraction.

Note:

  • We use currying to represent lambda abstractions with multiple arguments.
  • Arguments are not just names, they are patterns.
\a -> a -- Lambda (AsPattern WildcardPattern [ "a" ]) (Variable [ "a" ])

\a b -> a -- Lambda (AsPattern WildcardPattern [ "a" ]) (Lambda (AsPattern WildcardPattern [ "b" ]) (Variable [ "a" ]))
letDef : va -> Name -> Definition ta va -> Value ta va -> Value ta va

Represents a let expression that assigns a value (and optionally type) to a name.

Note: We use currying to represent let expressions with multiple name bindings.

let
    a =
        b
in
a
-- LetDef [ "a" ]
--     (UntypedDefinition [] (Variable [ "b" ]))
--     (Variable [ "a" ])


let
    a : Bool
    a =
        b


    c x =
        a
in
c
-- LetDef [ "a" ]
--     (TypedDefinition (Basic BoolType) [] (Variable [ "b" ]))
--     (LetDef [ "c" ]
--         (UntypedDefinition [ [ "x" ] ] (Variable [ "a" ]))
--         (Variable [ "c" ])
--     )
letDestruct : va -> Pattern va -> Value ta va -> Value ta va -> Value ta va

Represents a let expression that extracts values using a pattern.

let
    ( a, b ) =
        c
in
a
-- LetDestruct
--     (TuplePattern [ AsPattern WildcardPattern ["a"], AsPattern WildcardPattern ["b"] ])
--     (Variable ["a"])
letRec : va -> Dict Name (Definition ta va) -> Value ta va -> Value ta va

Represents a let expression with one or many recursive definitions.

let
    a =
        b


    b =
        a
in
a
-- LetRec
--     [ ( [ "a" ], UntypedDefinition [] (Variable [ "b" ]) )
--     , ( [ "b" ], UntypedDefinition [] (Variable [ "a" ]) )
--     ]
--     (Variable [ "a" ])
list : va -> List (Value ta va) -> Value ta va

A list represents an ordered list of values where every value has to be of the same type.

[ 1, 3, 5 ] -- List [ Literal (IntLiteral 1), Literal (IntLiteral 3), Literal (IntLiteral 5) ]


[] -- List []
record : va -> List ( Name, Value ta va ) -> Value ta va

A record represents a list of fields where each field has a name and a value.

{ foo = "bar" } -- Record [ ( [ "foo" ], Literal (StringLiteral "bar") ) ]


{ foo = "bar", baz = 1 } -- Record [ ( [ "foo" ], Literal (StringLiteral "bar") ), ( [ "baz" ], Literal (IntLiteral 1) ) ]


{} -- Record []
reference : va -> FQName -> Value ta va

A reference that refers to a function or a value with its fully-qualified name.

List.map -- Reference ( [ ..., [ [ "list" ] ], [ "map" ] )
tuple : va -> List (Value ta va) -> Value ta va

A tuple represents an ordered list of values where each value can be of a different type.

Note: Tuples with zero values are considered to be the special value Unit

( 1, True ) -- Tuple [ Literal (IntLiteral 1), Literal (BoolLiteral True) ]


( "foo", True, 3 ) -- Tuple [ Literal (StringLiteral "foo"), Literal (BoolLiteral True), Literal (IntLiteral 3) ]


() -- Unit
variable : va -> Name -> Value ta va

A variable represents a reference to a named value in the scope.

a -- Variable [ "a" ]


fooBar15 -- Variable [ "foo", "bar", "15" ]
ifThenElse : va -> Value ta va -> Value ta va -> Value ta va -> Value ta va

Represents and if/then/else expression.

if a then
    b
else
    c
-- IfThenElse (Variable ["a"])
--     (Variable ["b"])
--     (Variable ["c"])
patternMatch : va -> Value ta va -> List ( Pattern va, Value ta va ) -> Value ta va

Represents a pattern-match.

case a of
    1 ->
        "yea"


    _ ->
        "nay"
-- PatternMatch (Variable ["a"])
--     [ ( LiteralPattern (IntLiteral 1), Literal (StringLiteral "yea") )
--     , ( WildcardPattern, Literal (StringLiteral "nay") )
--     ]
update : va -> Value ta va -> List ( Name, Value ta va ) -> Value ta va

Update one or many fields of a record value.

{ a | foo = 1 } -- Update (Variable ["a"]) [ ( ["foo"], Literal (IntLiteral 1) ) ]
unit : va -> Value ta va

Represents the unit value.

() -- Unit
mapValueAttributes : (ta -> tb) -> (va -> vb) -> Value ta va -> Value tb vb

Pattern

Patterns are used in multiple ways in the IR: they can take apart a structured value into smaller pieces (destructure) and they can also filter values. The combination of these two features creates a very powerful method tool that can be used in two ways: destructuring and pattern-matching. Pattern-matching is a combination of destructuring, filtering and branching.

type Pattern a
= WildcardPattern a
| AsPattern a (Pattern a) Name
| TuplePattern a (List (Pattern a))
| ConstructorPattern a FQName (List (Pattern a))
| EmptyListPattern a
| HeadTailPattern a (Pattern a) (Pattern a)
| LiteralPattern a Literal
| UnitPattern a

Type that represents a pattern. A pattern can do two things: match on a specific shape or exact value and extract parts of a value into variables. It's a recursive data structure made of of the following building blocks:

  • WildcardPattern
    • Matches any value and does not extract any variables.
    • _ in Elm
  • AsPattern
    • Assigns a variable name to the value matched by a nested pattern.
    • (...) as foo in Elm
    • Special case: when there is just a variable name in a pattern in Elm it will be represented as a WildcardPattern wrapped in an AsPattern
  • TuplePattern
    • Matches on a tuple where each element matches the nested patterns.
  • ConstructorPattern
    • Matches on a type constructor and its arguments.
  • EmptyListPattern
    • Matches on an empty list.
  • HeadTailPattern
    • Matches on the head and the tail of a list.
    • Combined with EmptyListPattern it can match on lists of any specific sizes.
  • LiteralPattern
    • Matches an an exact literal value.
  • UnitPattern
    • Matches the Unit value only.

Matches any value and ignores it (assigns no variable name).

_ -- WildcardPattern
asPattern : a -> Pattern a -> Name -> Pattern a

Assigns a variable name to a pattern.

_ as foo -- AsPattern WildcardPattern ["foo"]


foo -- AsPattern WildcardPattern ["foo"]


[] as foo -- AsPattern EmptyListPattern ["foo"]
tuplePattern : a -> List (Pattern a) -> Pattern a

Destructures a tuple using a pattern for every element.

( _, foo ) -- TuplePattern [ WildcardPattern, AsPattern WildcardPattern ["foo"] ]
constructorPattern : a -> FQName -> List (Pattern a) -> Pattern a

Matches on a custom type's constructor.

Note: When the custom type has a single constructor this can be used for destructuring. When there are multiple constructors it also does filtering so it cannot be used in a LetDestruct but it can be used in a pattern-match.

Just _ -- ConstructorPattern ( ..., [["maybe"]], ["just"]) [ WildcardPattern ]

Matches an empty list. Can be used standalon but frequently used as a terminal pattern in a HeadTailPattern.

[] -- EmptyListPattern


[ _ ]
-- HeadTailPattern
--     WildcardPattern
--     EmptyListPattern

Matches the head and the tail of a list. It can be used to match lists of at least N items by nesting this pattern N times and terminating with EmptyListPattern.

[ a ]
-- HeadTailPattern
--     (AsPattern WildcardPattern ["a"])
--     EmptyListPattern


a :: b
-- HeadTailPattern
--     (AsPattern WildcardPattern ["a"])
--     (AsPattern WildcardPattern ["b"])


[ a, b ]
-- HeadTailPattern
--     (AsPattern WildcardPattern ["a"])
--     (HeadTailPattern
--         (AsPattern WildcardPattern ["b"])
--         EmptyListPattern
--     )

Matches a specific literal value. This pattern can only be used in a pattern-match since it always filters.

True -- LiteralPattern (BoolLiteral True)


'a' -- LiteralPattern (CharLiteral 'a')


"foo" -- LiteralPattern (StringLiteral "foo")


13 -- LiteralPattern (IntLiteral 13)


15.4 -- LiteralPattern (FloatLiteral 15.4)

Specification

The specification of what the value or function is without the actual data or logic behind it.

type alias Specification ta =
{ inputs : List ( Name, Type ta )
, output : Type ta
}

Type that represents a value or function specification. The specification of what the value or function is without the actual data or logic behind it.

mapSpecificationAttributes : (a -> b) -> Specification a -> Specification b

Definition

A definition is the actual data or logic as opposed to a specification which is just the specification of those. Value definitions can be typed or untyped. Exposed values have to be typed.

type alias Definition ta va =
{ inputTypes : List ( Name, va, Type ta )
, outputType : Type ta
, body : Value ta va
}

Type that represents a value or function definition. A definition is the actual data or logic as opposed to a specification which is just the specification of those. Value definitions can be typed or untyped. Exposed values have to be typed.

(Type ta -> Result e (Type ta))
-> (Value ta va -> Result e (Value ta va))
-> Definition ta va
-> Result (List e) (Definition ta va)
mapDefinitionAttributes : (ta -> tb) -> (va -> vb) -> Definition ta va -> Definition tb vb

Utilities

definitionToSpecification : Definition ta va -> Specification ta

Turns a definition into a specification by removing implementation details.

uncurryApply : Value ta va -> Value ta va -> ( Value ta va, List (Value ta va) )

Extract the argument list from a curried apply tree. It takes the two arguments of an apply and returns a tuple of the function and a list of arguments.

uncurryApply (Apply () f a) b == ( f, [ a, b ] )
collectVariables : Value ta va -> Set Name

Collect all variables in a value recursively.

collectDefinitionAttributes : Definition ta va -> List va
collectValueAttributes : Value ta va -> List va
indexedMapPattern : (Int -> a -> b) -> Int -> Pattern a -> ( Pattern b, Int )

Map attributes of a pattern while supplying an index to the map function. The index is incremented depth first.

indexedMapValue : (Int -> a -> b) -> Int -> Value ta a -> ( Value ta b, Int )

Map attributes of a value while supplying an index to the map function. The index is incremented depth first.

mapPatternAttributes : (a -> b) -> Pattern a -> Pattern b
valueAttribute : Value ta va -> va
definitionToValue : Definition ta () -> Value ta ()

Turn a value definition into a value by wrapping the body value as needed based on the number of arguments the definition has. For example, if the definition specifies 2 inputs it will wrap the body into 2 lambdas each taking one argument:

definitionToValue
    (Definition
        { inputTypes =
            [ ( [ "foo" ], (), intType () )
            , ( [ "bar" ], (), intType () )
            ]
        , outputType =
            intType ()
        , body =
            Tuple () [ Variable () [ "foo" ], Variable () [ "bar" ] ]
        }
    )
-- Lambda (AsPattern () (WildcardPattern ()) [ "foo" ])
--     (Lambda (AsPattern () (WildcardPattern ()) [ "bar" ])
--         (Tuple () [ Variable () [ "foo" ], Variable () [ "bar" ] ])
--     )