Section 4: Magic Expressions
Magic expressions are valid basic expression in disguise but are treated differently by the compiler.
4.1 Objects
Objects can be viewed as list of key-value pairs, where the values can be heterogeneous.
4.1.1 Object construction
To create a object, we need to use the following grammar:
$
.
{ propertyId(
propertyValue)
}
propertyId can be any valid constant identifier, 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 polyfunc invocation, 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)
.
4.1.2 Property getter
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,
Because property getters are actually unifunc invocation, we cannot declare unifunc which takes the name of any property the given object. For example, the following introduces a compile error:
4.1.3 Property setter
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:
objectExpr
.
propertyId(
newValue | lambda)
4.1.3.1 Direct value update
Direct value update refers to the fact that we update the value of a property without referencing its original value.
For example,
4.1.3.2 Referential value update
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%.
4.1.3.3 Function signature clashing
Since property setters are actually polyfunc invocation, 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.
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
.
4.1.4 Aliased constructor
Refer Section 5.5.1.
4.2 Tag constructors
4.2.1 Carryless tag constructor
Refer Section 5.4.1.1.
4.2.2 Carryful tag constructor
Refer Section 5.4.1.2.
4.3 Tag matchers
Tag matchers (a.k.a case expression) 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 tagged union. For the sake of demonstration, we will use the following tagged union Shape
for further explanation.
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.
4.3.1 Exhaustive matching
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.
4.3.2 Non-exhaustive matching
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
4.3.3 Branch homogeneity
All branches must have the same type as the first branch. Thus, the following code is invalid:
4.3.4 Static analysis
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.
4.3.5 Optional bindings
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:
4.4 Foreign function interface (FFI)
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.
Last updated
Was this helpful?