Lambda expressions
This page is part of the magic.node documentation.
Lambda expressions are kind of like XPath expressions, except they will reference nodes in your Node graph object instead of XML nodes. Below is an example to give you an idea.
.foo:hello world
get-value:x:@.foo
// After invocation of the above **[get-value]**, its value will be "hello world".
Most slots in Magic can accept expressions to reference nodes, values of nodes, and children of nodes somehow. This allows you to modify the lambda graph object, as it is currently being executed, and hence allows you to modify “anything” from “anywhere”. This resembles XPath expressions from XML.
Iterators in lambda expressions
An expression is constructed from one or more “iterator”. This makes an expression
become “dynamically chained Linq statements”, where each iterator reacts upon
the results of its previous iterator. Each iterator takes as input an IEnumerable,
and returns as its result another IEnumerable, where the content of the iterator
somehow changes its given input, according to whatever the particular iterator’s
implementation does. This approach just so happens to be perfect for retrieving
sub-sections of graph objects.
Each iterator ends with a ”/” or a CR/LF sequence, and before its end, its value defines what it does. For instance the above iterator in the [get-value] invocation, starts out with a ”@”. This implies that the iterator will find the first node having a name of whatever follows its ”@”. For the above this implies looking for the first node who’s name is “.foo”. To see a slightly more advanced example, imagine the following.
.data
item1:john
item2:thomas
item3:peter
get-value:x:@.data/*/item2
It might help to transform the above expression into humanly readable language. Its English equivalent hence becomes as follows.
Find the node with the name of ‘.data’, then retrieve its children, and filter away everything not having a name of ‘item2’
Of course, the result of the above becomes “thomas”.
Below is a list of all iterators that exists in magic. Substitute “xxx” with a string, “n” with a number, and “x” with an expression.
*Retrieves all children of its previous result#Retrieves the value of its previous result as a node by reference-Retrieves its previous result set’s “younger sibling” (previous node)+Retrieves its previous result set’s “elder sibling” (next node).Retrieves its previous result set’s parent node(s)^xxxRetrieves the first ancestor node with the specified “xxx” name. Similar to@iterator but does not traverse siblings, only direct ancestors up in hierarchy..Retrieves the root node**Retrieves its previous result set’s descendant, with a “breadth first” algorithm--Retrieves all ancestors and older siblings upwards in object!xxxTraverses all descendants except those matching specified “xxx” name and returns{x}Extrapolated expression that will be evaluated assuming it yields one result, replacing itself with the value of whatever node it points to=xxxRetrieves the node with the “xxx” value, converting to string if necessary[n,n]Retrieves a subset of its previous result set, implying “skip, count”, or “offset, count”@xxxReturns the first node “before” in its hierarchy that matches the given “xxx” in its namen(any number) Returns the n’th child of its previous result set[x|y]Pipe separated list of names returning all nodes having a name of either “x” or “y”
Notice, you can escape iterators by using backslash “\”. This allows you to look for nodes who’s names
are for instance “3”, without using the n’th child iterator, which would defeat the purpose. In addition,
you can quote iterators by using double quotes ", to allow for having iterators with values that are normally
not legal within an iterator, such as /, etc. If you quote an iterator you have to quote the entire expression.
Below is an example of a slightly more advanced expression.
.foo
howdy:wo/rld
jo:nothing
howdy:earth
.dyn:.foo
for-each:x:@"./*/{@.dyn}/*/""=wo/rld"""
set-value:x:@.dp/#
:thomas was here
After evaluating the above Hyperlambda, the value of all nodes having “wo/rld” as their value inside of [.foo] will be updated to become “thomas was here”. Obviously, the above expression is a ridiculously complex example, that you will probably never encounter in your own code. However, for reference purposes, let’s break it down into its individual parts.
- Get parent node
- Get all children
- Filter away everything not having the name of the value of
{@.dyn}, which resolves to the equivalent of:x:@.dyn, being an expression, who’s result becomes “.foo”. - Get its children
- Find all nodes who’s value is “wo/rld”.
98% of your expressions will have 1-3 iterators, no complex escaping, and no parameters. And in fact, there are thousands of lines of Hyperlambda code in Magic’s middleware, and 98% of these expressions are as simple as follows.
.arguments
foo1:string
get-value:x:@.arguments/*/foo1
Which translates into the following English.
Give me the value of any [foo1] nodes, inside of the first [.arguments] node you can find upwards in the hierarchy.
Expressions can also be extrapolated, which allows you to parametrise your expressions, by nesting expressions, substituting parts of your expression dynamically as your code is executed. Imagine the following example.
.arg1:foo2
.data
foo1:john
foo2:thomas
foo3:peter
get-value:x:@.data/*/{@.arg1}
The above expression will first evaluate the {@.arg1} parts, which results in “foo2”, then evaluate the
outer expression, which now will look like this @.data/*/foo2. You can also extrapolate expressions on
values, such as illustrated below.
.arg1:thomas
.data
foo1:john
foo2:thomas
foo3:peter
get-name:x:@.data/*/={@.arg1}
Extending lambda expressions/iterators
You can easily extend expressions in Magic, either with a “static” iterator, implying a direct match - Or with a dynamic parametrized iterator, allowing you to create iterators that requires “parameters”. To extend the supported iterators, use any of the following two static methods.
Iterator.AddStaticIterator- Creates a “static” iterator, implying a direct match.Iterator.AddDynamicIterator- Creates a “dynamic iterator create function”.
Below is a C# example, that creates a dynamic iterator, that will only return nodes having a value,
that once converted into a string, has exactly n characters, not less and not more.
Iterator.AddDynamicIterator('%', (iteratorValue) => {
var no = int.Parse(iteratorValue.Substring(1));
return (identity, input) => {
return input.Where(x => x.Get<string>()?.Length == no);
};
});
var hl = @"foo
howdy1:XXXXX
howdy2:XXX
howdy3:XXXXX
";
var lambda = HyperlambdaParser.Parse(hl);
var x = new Expression("../**/%3");
var result = x.Evaluate(lambda);
Notice how the iterator we created above, uses the %3 parts of the expression, to parametrize
itself. If you exchange 3 with 5, it will only return [howdy1] and [howdy3] instead,
since it will look for values with 5 characters instead. The Iterator class can be found
in the magic.node.extensions namespace.
You can use the above syntax to override the default implementation of iterators, although
I wouldn’t recommend it, since it would create confusion for others using your modified version.
Notice - To create an extension iterator is an exercise you will rarely if ever need to do, but is included here for reference purposes.
Parsing Hyperlambda from C#
Magic allows you to easily parse Hyperlambda from C# if you need it, which can be done as follows.
using magic.node.extensions.hyperlambda;
var hl = GetHyperlambdaAsString();
var lambda = HyperlambdaParser.Parse(hl);
The GetHyperlambdaAsString above could for instance load Hyperlambda from a file, retrieve it
from your network, or some other way retrieve a snippet of Hyperlambda text. The HyperlambdaParser.Parse
parts above will return your Hyperlambda as its Node equivalent. The Parser class also has an
overloaded constructor for taking a Stream instead of a string.
Notice - The Node returned above will be a root node, wrapping all nodes found in your
Hyperlambda as children nodes. This is necessary in order to avoid enforcing a single “document node”
the way XML does.
Once you have a Node object, you can easily reverse the process by using the HyperlambdaGenerator
class, and its GetHyperlambda method such as the following illustrates.
using magic.node.extensions.hyperlambda;
var hl1 = GetHyperlambdaAsString();
var result = HyperlambdaParser.Parse(hl1);
var hl2 = HyperlambdaGenerator.GetHyperlambda(result.Children);
| ← Hyperlambda types | all of magic.node |