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 () (WholeNumberLiteral 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 () (WholeNumberLiteral 2))
Value is the top level building block for data and logic. See the constructor functions below for details on each node type.
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:
Apply
nodes depending on the number
of arguments.Value
.Value
.Value
.foo.bar
.bar
Apply
nodes in each other (currying).LetDefinition
will not make recursion possible due to its scoping rules.Value () ()
A value without any additional information.
Value () (Morphir.IR.Type.Type ())
A value with type information.
literal : va -> Morphir.IR.Literal.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 (WholeNumberLiteral 13)
15.4 -- Literal (FloatLiteral 15.4)
constructor : va -> Morphir.IR.FQName.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 -> Morphir.IR.Name.Name -> Value ta va
Extracts the value of a record's field.
a.foo -- Field (Variable [ "a" ]) [ "foo" ]
fieldFunction : va -> Morphir.IR.Name.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:
\a -> a -- Lambda (AsPattern WildcardPattern [ "a" ]) (Variable [ "a" ])
\a b -> a -- Lambda (AsPattern WildcardPattern [ "a" ]) (Lambda (AsPattern WildcardPattern [ "b" ]) (Variable [ "a" ]))
letDef : va -> Morphir.IR.Name.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 Morphir.IR.Name.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 (WholeNumberLiteral 1), Literal (WholeNumberLiteral 3), Literal (WholeNumberLiteral 5) ]
[] -- List []
record : va -> Dict Morphir.IR.Name.Name (Value ta va) -> Value ta va
A record represents a dictionary of fields where the keys are the field names, and the values are the field values
{ foo = "bar" } -- Record [ ( [ "foo" ], Literal (StringLiteral "bar") ) ]
{ foo = "bar", baz = 1 } -- Record [ ( [ "foo" ], Literal (StringLiteral "bar") ), ( [ "baz" ], Literal (WholeNumberLiteral 1) ) ]
{} -- Record []
reference : va -> Morphir.IR.FQName.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 (WholeNumberLiteral 1), Literal (BoolLiteral True) ]
( "foo", True, 3 ) -- Tuple [ Literal (StringLiteral "foo"), Literal (BoolLiteral True), Literal (WholeNumberLiteral 3) ]
() -- Unit
variable : va -> Morphir.IR.Name.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 (WholeNumberLiteral 1), Literal (StringLiteral "yea") )
-- , ( WildcardPattern, Literal (StringLiteral "nay") )
-- ]
update : va -> Value ta va -> Dict Morphir.IR.Name.Name (Value ta va) -> Value ta va
Update one or many fields of a record value.
{ a | foo = 1 } -- Update (Variable ["a"]) [ ( ["foo"], Literal (WholeNumberLiteral 1) ) ]
unit : va -> Value ta va
Represents the unit value.
() -- Unit
mapValueAttributes : (ta -> tb) -> (va -> vb) -> Value ta va -> Value tb vb
rewriteMaybeToPatternMatch : Value ta va -> Value ta va
Rewrite "... |> Maybe.map .. |> Maybe.withDefault ..." to a pattern match with a Just and a Nothing branch
replaceVariables : Value ta va -> Dict Morphir.IR.Name.Name (Value ta va) -> Value ta va
Find and replace variables with another value based on the mapping provided.
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 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:
_
in Elm(...) as foo
in ElmWildcardPattern
wrapped in an AsPattern
EmptyListPattern
it can match on lists of any specific sizes.Unit
value only.wildcardPattern : a -> Pattern a
Matches any value and ignores it (assigns no variable name).
_ -- WildcardPattern
asPattern : a -> Pattern a -> Morphir.IR.Name.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 -> Morphir.IR.FQName.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 ]
emptyListPattern : a -> Pattern a
Matches an empty list. Can be used standalon but frequently used as a terminal pattern
in a HeadTailPattern
.
[] -- EmptyListPattern
[ _ ]
-- HeadTailPattern
-- WildcardPattern
-- EmptyListPattern
headTailPattern : a -> Pattern a -> Pattern a -> Pattern a
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
-- )
literalPattern : a -> Morphir.IR.Literal.Literal -> Pattern a
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 (WholeNumberLiteral 13)
15.4 -- LiteralPattern (FloatLiteral 15.4)
The specification of what the value or function is without the actual data or logic behind it.
{ inputs : List ( Morphir.IR.Name.Name
, Morphir.IR.Type.Type ta )
, output : Morphir.IR.Type.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
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.
{ inputTypes : List ( Morphir.IR.Name.Name
, va
, Morphir.IR.Type.Type ta )
, outputType : Morphir.IR.Type.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.
mapDefinition : (Morphir.IR.Type.Type ta -> Result e (Morphir.IR.Type.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
definitionToSpecification : Definition ta va -> Specification ta
Turns a definition into a specification by removing implementation details.
typeAndValueToDefinition : Morphir.IR.Type.Type ta -> Value ta va -> Definition ta va
Moves lambda arguments into function arguments as much as possible. For example given this function definition:
foo : Int -> Bool -> ( Int, Int ) -> String
foo =
\a ->
\b ->
( c, d ) ->
doSomething a b c d
It turns it into the following:
foo : Int -> Bool -> ( Int, Int ) -> String
foo a b =
( c, d ) ->
doSomething a b c d
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 Morphir.IR.Name.Name
Collect all variables in a value recursively.
collectReferences : Value ta va -> Set Morphir.IR.FQName.FQName
Collect all references in a value recursively.
collectDefinitionAttributes : Definition ta va -> List va
collectPatternAttributes : Pattern a -> List a
collectValueAttributes : Value ta va -> List va
indexedMapPattern : (Basics.Int -> a -> b) -> Basics.Int -> Pattern a -> ( Pattern b, Basics.Int )
Map attributes of a pattern while supplying an index to the map function. The index is incremented depth first.
indexedMapValue : (Basics.Int -> a -> b) -> Basics.Int -> Value ta a -> ( Value ta b, Basics.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
patternAttribute : Pattern a -> a
valueAttribute : Value ta va -> va
definitionToValue : Definition ta va -> Value ta va
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" ] ])
-- )
rewriteValue : (Value ta va -> Maybe (Value ta va)) -> Value ta va -> Value ta va
Recursively rewrite a value using the supplied mapping function.
toRawValue : Value ta va -> RawValue
Clear all type and value annotations to get a raw value.
countValueNodes : Value ta va -> Basics.Int
collectPatternVariables : Pattern va -> Set Morphir.IR.Name.Name
Collect all variables in a pattern.
isData : Value ta va -> Basics.Bool
Check if the value has any logic in it all is all just data.
toString : Value ta va -> String
Simple string version of a value tree. The output is mostly compatible with the Elm syntax except where Elm uses indentation to separate values. This representation uses semicolons in those places.
generateUniqueName : Value ta va -> Morphir.IR.Name.Name
Generate a unique name that is not used in the given value
reduceValueBottomUp : (Value typeAttribute valueAttribute -> List accumulator -> accumulator) -> Value typeAttribute valueAttribute -> accumulator
Function to traverse a value expression tree bottom-up and apply a function to an accumulator value at each step to come up with a final value that was derived from the whole tree. It's very similar to a fold but there are a few differences since it works on a tree instead of a list.
The function takes a lambda that will be invoked on each node with two arguments:
The lambda should calculate and return an accumulator value that will be continuously rolled up to the top of the expression tree and returned at the root.
This is a very flexible utility function that can be used to do a lot of things by supplying different lambdas. Here are a few examples:
reduceValueBottomUp (\_ childDepths -> (List.maximum childDepths |> Maybe.withDefault 0) + 1)
These are simple examples that return a single value but you could also use a more complex accumulator value. For example you could collect things by using a list accumulator or build a new tree.