Types and conversion
Types and conversion
These slots allow you to inspect and convert between Hyperlambda types, in addition to returning the vocabulary of slots your cloudlet understands.
How to use [types]
This slot returns all Hyperlambda types your current installation supports.
types
How to use [type]
This slot returns the Hyperlambda type name of some value.
.foo:int:57
type:x:-
After invoking the above, the value of [type] will be int.
How to use [convert]
This slot converts the value of an expression from its original type, to whatever [type] declaration you supply as its argument.
.foo:57
convert:x:-
type:int
Notice - You can also base64 encode and decode byte[] with this slot, by passing in “base64” or
“from-base64” as your [type] argument. The value of the object you want to convert must obviously support
conversion to the specified type, and will throw an exception if no conversion is possible, such as if you
for instance try to convert the string of “foo-bar” to an integer.
How to use [format]
This slot converts some expression or value according to some specified String.Format pattern.
The following code will string format the number 57 to make sure it’s prefixed with leading zeros, always ending
up having at least 5 digits. See .Net String.Format method for which patterns you can use. The culture used will
be the invariant one, unless you explicitly override the culture with a [culture] argument.
.foo:int:57
format:x:-
pattern:"{0:00000}"
To override the [culture] argument used you can use something such as follows.
.foo:decimal:57
format:x:-
pattern:"{0:0.00}"
culture:nb-NO
Since the above is using Norwegian Bokmal as its [culture] argument, it will return the result with a ,
as a decimal separator instead of the default which is .. You can use the same [pattern] values for
this slot as you can use in C# and .Net. To see an extensive list of for instance the formatting patterns you
can use for a date and time object, you can browse the Microsoft documentation
related to this. If you don’t supply a [culture] argument the invariant culture will be used.
How to use [int2words]
This slot takes a signed 64 bit integer and converts it to its word representation, such as for instance 1200050 becoming “one million two hundred thousand and fifty”. Below is an example of using the slot.
int2words:1200050
The slot works with signed 64 bit integer values, implying it can work with numbers in the trillions if required. The result of executing the above should resemble the following.
int2words:one million two hundred thousand and fifty
The slot is particularly useful for RAG and VSS databases, where embedding models such as “text-embedding-ada-002” tends to do a much better job at matching numbers if you spell these out instead of simply providing them as numbers.
How to use [vocabulary]
Returns the name of every static slot in your system, optionally passing in a string, or an expression leading to a string, which is a filtering condition, where the slot must start with the filter specified to be considered a part of the end result.
// Returns ALL slots in your system.
vocabulary
// Returns only slots starting with [io.file]
vocabulary:io.file
Notice, inside a [whitelist] invocation this slot only returns the slots the whitelist allows, so it tells the code running inside what it can actually invoke. A whitelisted slot that is pinned to an argument is returned once by name, regardless of how many different arguments it is pinned to.
How to use [whitelist]
This slot temporarily within the given scope changes the available slots, allowing you to declare a block of lambda, where only a sub-set of your vocabulary is available for some piece of code to invoke. This allows you to relatively securely allow some partially untrusted source to pass in a piece of Hyperlambda, for then to allow it to evaluate its own Hyperlambda. The slot takes two arguments.
- [vocabulary] - Whitelisted slots
- [.lambda] - Lambda object to evaluate for the given scope
.result
whitelist
vocabulary
set-value
return
.lambda
// Inside of this [.lambda] object, we can only invoke [set-value], and no other slots!
set-value:x:@.result
.:foo
// Notice, the next line will throw an exception if you remove its "." character,
// because [add] is not whitelisted in our above [vocabulary] declaration!
.add:x:@.result
.
foo:bar
return
result:success
For security reasons the [whitelist] invocation’s [.lambda] object is immutable, and the caller cannot access nodes outside of the [.lambda] object itself, which prohibits the caller to modify, and/or read nodes from outside of its [whitelist] invocation. In addition a [whitelist] invocation creates its own result stack object, allowing the [whitelist] invocation to return values and nodes to the caller using for instance the [return] slot. If you execute the above Hyperlambda you can see how this semantically works by realizing how the above [.result] node never has its value actually changed, because our invocation to [set-value] inside our whitelist invocation yields a “null node-set result”. Hence, semantically the [whitelist] slot works the same way signaling a dynamic slot works in Hyperlambda, in that the invocation treats its [.lambda] object as if it was a dynamic slot, isolating it from the rest of our code, allowing the lambda object to return values and nodes to the caller.
Pinning arguments in your [vocabulary]
A [vocabulary] entry can optionally declare a value, which pins the slot to that exact argument. An entry without a value allows the slot with any argument, exactly as above, while an entry with a value only allows the slot when its argument matches.
.result
whitelist
vocabulary
data.connect:chinook
data.read
return-nodes
.lambda
// Legal, since the vocabulary pins [data.connect] to exactly this database.
data.connect:chinook
data.read
table:Artist
limit:5
return-nodes:x:@data.read/*
With the above vocabulary, data.connect:northwind throws, because the argument does not match the
pin. Repeat the same slot name with different values to allow more than one argument. This is how you
confine a piece of untrusted Hyperlambda to one specific database, one specific URL, or one specific
dynamic slot, instead of merely to the name of a slot.
Notice, dynamic slots are pinned the same way, through the slot that invokes them.
vocabulary
signal:accounting.report
signal:hr.hire
The above allows the caller to invoke those two dynamic slots, and no others.
Files are pinned the same way, through [execute-file] and [io.file.execute], which lets you admit a specific Hyperlambda file instead of the slots it uses.
vocabulary
execute-file:/modules/reports/top-customers.hl
io.file.execute:/modules/reports/top-customers.hl
io.file.load:/modules/reports/top-customers.hl
The statements inside an admitted file, or inside an admitted dynamic slot, are not restricted by the whitelist, so the file can invoke slots that the caller itself cannot. This lets you expose complex business logic to untrusted code without exposing the raw resources it works on, as long as you trust the file, and the file validates the arguments it is given. The [io.file.load] line is optional, and allows the caller to read the file’s source. Read more in the Sandbox API generator.
Wildcards in pinned arguments
A pin ending with an asterisk matches any argument starting with the rest of the pin. Anything without an asterisk is an exact comparison, such that existing vocabularies keep behaving as before.
vocabulary
data.connect:test-*
http.post:"https://api.example.com/v3/*"
File and folder slots interpret their argument as a path, and compare it segment by segment, which confines a wildcard to the segment it occurs in, the same way a wildcard is confined in a shell.
- /etc/* - Every file directly in the folder, but not in its sub folders
- /etc/*.md - Every markdown file directly in the folder
- /etc/*.tar.gz - Multi part extensions work the same way
- /etc/*/config.json - That filename, one folder level below
A folder ends with a slash by convention in Magic, hence a folder is pinned with its trailing slash.
vocabulary
io.file.load:/etc/*.md
io.folder.create:/etc/*/
An asterisk must be either an entire segment, or the start of the filename. Anything else throws,
rather than silently matching something its author did not intend - such as /etc/f*o.md, or an
extension on a folder segment such as /etc/foo.*/howdy/*. A folder has no extension, so /etc/*.md/
throws for a folder slot even though the same wildcard is legal as a filename.
Notice, a slot that reads a file on your behalf does so through the file slot, and is therefore subject to the same pins. Attaching a file to an email with [mail.smtp.send], hashing a file with [crypto.hash], uploading one with [http.post], or resizing an image, all require the relevant [io.stream.open-file] or [io.stream.save-file] pin in addition to the slot itself.
How to use [context]
This slot allows you to add an object unto the stack, such that it can later be retrieved with the [get-context] slot. Below is an example.
.result
context:foo
value:bar
.lambda
set-value:x:@.result
get-context:foo
The slot requires a name as the value of its slot invocation node, a [value] as the value you want to put onto the stack, and a [.lambda] object being the lambda where the stack object exists, and can be retrieved using [get-context]. This is quite useful if you have some piece of data that needs to be accessible through the entirety of the execution of some Hyperlambda snippet, implying also for slots you invoke, where you don’t want to pass in the data as an argument to the slot itself. Imagine the following to understand how this works.
slots.create:bar
get-context:foo
return:x:-
context:foo
value:Context value
.lambda
signal:bar
If you execute the above Hyperlambda you will notice how the slot called [bar] actually has access to the context value called “foo”, and can retrieve this. This feature allows you to declare and create “long lasting arguments” that are accessible from within the entirety of a piece of Hyperlambda, including each slot it invokes, and/or Hyperlambda files it executes.
How to use [apply]
This slot takes an expression in addition to a list of arguments, and “applies” the arguments unto the expression’s result node set, allowing you to perform dynamic substitutions on lambda hierarchies such as the following illustrates.
.lambda
foo
arg1:{some_arg}
some-static-node:static-value
apply:x:-
some_arg:value of argument
After execution of the above Hyperlambda you will have a result resembling the following.
apply
foo
arg1:value of argument
some-static-node:static-value
Notice how the nodes from your template lambda object have been copied as children into your [apply]
node, while during this “copying process”, the arguments you supplied to [apply] have been used
to perform substitutions on all nodes in your template lambda having a value of {xxx}, where xxx is
your argument name. In the above example for instance the arg1:{some_arg} template node had its value
replaced by the value of the [some_arg] node passed in as a parameter to [apply]. If the name of
the argument to [apply], matches the value of your template node wrapped inside curly braces - Then
the value of your argument to apply becomes the new value of your template node after substitution has
been performed.
Only node values starting out with { and ending with } will be substituted, and you are expected to provide
all arguments found in the template lambda object, or the invocation will fail, resulting in an exception. This
allows you to create “template lambda objects” that you dynamically transform into something else, without
really caring about its original structure, but rather only its set of dynamic substitution arguments. This
slot leaves all other nodes as is.
Basically, the way it works, is that it takes your expression, and recursively iterates each node below the
result of your expression, checks to see if the node’s value is an argument such as e.g. {howdy} -
And if so, it substitutes the {howdy} parts with the value of the argument you are expected to supply to your
invocation having the name [howdy].
You can also reference the same argument multiple times in your template, in addition to create arguments that are lambda objects instead of simple values. Below is an example of both of these constructs.
.lambda
foo
foo1:{arg1}
foo2:{arg1}
foo3:{arg2}
apply:x:-
arg1:int:5
arg2
this:is
an:entire
lambda:object
used_as_an_argument:int:7
The above will result in the following.
apply
foo
foo1:int:5
foo2:int:5
foo3
this:is
an:entire
lambda:object
used_as_an_argument:int:7
Above you can also see how typing information is not lost, since the [foo1] and [foo2] nodes are both having integer values of 5 after apply is done executing. At the same time you can see how the [foo3] node instead of having a simple value applied, actually ended up with a copy of all children passed in as [arg2]. You can also substitute names of nodes, such as the following illustrates.
.lambda
foo
{arg1}:value-is-preserved
apply:x:-
arg1:foo1
The above of course results in the following.
apply
foo
foo1:value-is-preserved
This is a fairly advanced slot, but it’s the heart of the generator, allowing us to generate HTTP endpoints, starting out with some template file, which is dynamically changed, according to input arguments supplied during the crudification process. You can also combine transformations of names, values, and children in the same template nodes. The process is also recursive in nature, performing substitutions through the entire hierarchy of your template lambda.
How to use [sort]
Sort takes an expression leading to a list of nodes, in addition to a lambda object. Your lambda object will be invoked several times, with an [.lhs], an [.rhs] and a [.result] argument. It is your code’s responsibility to set the value of the [.result] node according to which of the [.lhs] and [.rhs] node should come first in the sorted result.
.data
.
name:Thomas
.
name:John
.
name:Jane
.
name:Peter
sort:x:@.data/*
if
lt:x:@.lhs/#/*/name
get-value:x:@.rhs/#/*/name
.lambda
set-value:x:@.result
.:int:-1
else-if
mt:x:@.lhs/#/*/name
get-value:x:@.rhs/#/*/name
.lambda
set-value:x:@.result
.:int:1
else
set-value:x:@.result
.:int:0
Notice, [sort] does not modify the original list, but returns a new list of nodes after having executed.