Section 4: Magic Expressions
Last updated
Was this helpful?
Last updated
Was this helpful?
Magic expressions are valid basic expression in disguise but are treated differently by the compiler.
Objects can be viewed as list of key-value pairs, where the values can be heterogeneous.
To create a object, we need to use the following grammar:
$
.
{ propertyId(
propertyValue)
}
propertyId can be any valid , while propertyValue can be any valid expression.
For example, the following is equivalent to JSON {"name": "Keli", "age": 20}
. In fact, it is actually a , where the parameters are $
, "Keli"
and 20
while the function name is name
age
.
Since Keli supports structural typing, the expression above bears the type $.name(String) age(Int)
.
Property getters are for accessing the value of a object given a key. Property getters can be invoked via the following grammar:
objectExpr
.
propertyId
For example,
Property setters are for replacing value of a object given a key. Since Keli does not allow implicit mutations, each invocation of a property setter will return a new copy of the specified object.
Property setters is a polymorphic function that can either takes in a new value, or a lambda that can be used for referential update.
Property setters can be invoked via the following grammar:
Direct value update refers to the fact that we update the value of a property without referencing its original value.
For example,
Referential value update means that we want to update the value of a property by applying some functions to its original value.
This feature can also be known as lambda setters.
Consider the following example where person
wants to decrease its car's price by 10%.
However, since Keli supports multiple dispatch, it is possible to define a polyfunc that takes 2 parameters where its identifier matches one of the property name of the subject (the first parameter) as long as the second parameter does NOT have the same type as of the corresponding property setters.
That is to say, the following function declaration is valid, because the second parameter type is Int
not String
.
Tag matchers are magic functions that can only be invoked on expression that have the type of tagged union, and they can be invoked using the following grammar:
tagExpr
.
{ carrylessTagBranch | carryfulTagBranch | elseBranch }
where:
carrylessTagBranch =
if
(
.
tagId)
:
**(
branchExpr)
carryfulTagBranch =
if
(
.
tagId(
constId)
})
:
(
branchExpr)
defaultBranch =
else
(
branchExpr)
There are two kinds of tag matchers, namely exhasutive and non-exhaustive.
Exhaustive matching means every possible tag is matched. Consider the following function where the first parameter is type of Shape
.
Example output :
Input
Output
Shape.Circle(3).area
28.2743
Shape.Rectangle($.height(3) width(4)).area
12.0
Shape.Empty
0.0
The indentation presented in the code above is just for formatting purpose, as Keli is not indentation-sensitive.
Non-exhaustive matching means not all possible tags are listed, however one of the tag must be an else
.
For example,
Sample output:
Input
Output
Shape.Circle(3).isEmpty
Boolean.false
Shape.Rectangle($.height(3) width(4)).isEmpty
Boolean.false
Shape.Empty.isEmpty
Boolean.true
All branches must have the same type as the first branch. Thus, the following code is invalid:
A valid tag matcher must satisfy the following criteria:
No duplicated tag
Must be exhaustive unless the else
branch is present.
No undefined tag (in the sense that it is not defined in the corresponding tagged union).
No more than one else
branch is present for non-exhaustive matching.
At certain situation, the bindings of a carryful tag might not be fully utilized, in such cases, we can simply not specify those unneeded properties.
For example, suppose we have the following tagged union:
And a function to check if an Animal
can fly or not:
In the function above, we can see that bindings b
and c
are not used at all, so we actually erase them out to have a less noisy code as follows:
Foreign function interface is used to invoked JavaScript function from Keli. FFI is a kind of expression that can be created using the following grammar:
ffi
.
javascript
(
"
javascriptCode"
)
The type of an FFI expression is undefined
, thus we need to cast it explicitly using the magic function as
.
Every identifier in Keli can be used in the JavaScript code by prefixing them with k$
. For example, if the identifier is x
is Keli, then it will be transpiled as k$x
in JavaScript.
Moreover, since the Boolean
type is not built-in to Keli, but rather defined in the Prelude, we cannot use True
or False
in JavaScript, but rather k$Boolean.k$True
or k$Boolean.k$False
. Similarly, any other tagged union can be returned from the JavaScript code using the following grammar:
$
taggedUnionId.
$
tagId
For example,
Warning
FFI must be used safely, because annotating foreign functions with incorrect type annotation will allow bugs to slip through type checking and result in run-time error that is hard to debug.
Because property getters are actually , we cannot declare unifunc which takes the name of any property the given object. For example, the following introduces a compile error:
objectExpr .
propertyId (
newValue | )
Since property setters are actually , we cannot declare polyfunc that takes 2 parameters where its identifier matches one of the property name of the subject (the first parameter) and the second parameter have the same type as of the corresponding property setters.
Refer .
Refer .
Refer .
Tag matchers (a.k.a ) is the heart of control structure in Keli, as Keli does not provide any if-else structure.
Before we use tag matchers, we must first defined a . For the sake of demonstration, we will use the following tagged union Shape
for further explanation.