-
Notifications
You must be signed in to change notification settings - Fork 3
Useful Syntax
Khepri purposely adds as few language features to Javascript as possible, instead preferring to extend Javascript with additionally expressive power. So while Khepri does not include fancy features like proper range based for loops, it does allow programmers to implement their own language constructs with fairly low overhead and a nice syntax.
This page looks at a few useful ways Khepri elements can be combined for additional expressive power.
By using the application syntax and a lambda, it is possible to write custom code that appears similar to builtin statements, such as loops.
// Create a thunk that when invoked evaluates `f`.
var lazy := \f -> \ -> f();
var x, y;
lazy \-> x + y;
lazy \-> {
// Some expensive ops
};
Currying allows multiple args to be passed to the callee without parens:
// Simple for each style loop
var forEach := \a f -> a.forEach(f);
forEach @ [1, 2, 3, 4] \ x ->
console.log(x);
forEach @ [[1, 2], [2, 3]] \ [a b] -> {
console.log(a);
console.log(b);
};
Like in F#, Khepri can use the pipeline operator and currying to create powerful but imperative looking sequences of operations.
In the zipper library [Neith][neith], all operations are set up to allow easy pipelining. In normal Javascript, you would typical have to write code like this:
stream.toArray(
zipper.extract(
zipper.root(
zipper.insertLeft(
0,
zipper.down(
zipper.child(
'a',
list.listZipper(
nestedList))))))));
The reverse evaluation order of this expression makes it difficult to read, and the parens make it difficult to maintain. Using pipes and currying we can instead write:
nestedList
|> nestedList
|> list.listZipper
|> zipper.child @ 'a'
|> zipper.down
|> zipper.insertLeft @ 0
|> zipper.root
|> zipper.extract
|> stream.toArray;
Which reads like a set of imperative statements. Currying allows passing multiple arguments to the pipeline states with very little overhead.
Bennu parser combinators often need to be annotated using other parsers (higher order functions) so they can generate readable errors messages.
// Here, the expected parser tells the user what is
// expected in the error message if parsing fails.
label('Arguments',
between(punctuator '(', punctuator ')',
sepBy(punctuator ',',
expected('argument', expression))))
However the syntax for expected
is a bit unnatural to read and the call to label requires parens around the entire expr. Instead, we can used pipe operators and currying to make the expression more readable.
label @ 'Arguments' <|
between(punctuator '(', punctuator ')',
sepBy(punctuator ',',
expression |> expected@'argument'))
Using the ??
operator and a dot expression allows safe accessor expression to be written.
// This will crash if any of these members is not an object
a.b.c.d.e;
But the ??
only evaluates its left hand side, if the right hand side is a valid object.
// No matter what these members are, we wont crash
a??.b??.c??.d??.e;