Bend/docs/builtin-defs.md
2024-04-03 20:47:47 +02:00

4.1 KiB

Builtin Definitions

HVM-lang includes some built-in definitions that can make certain tasks easier, such as debugging, benchmarking, etc. These definitions are "special", and can't be implemented using regular HVM code.

On errors.

Some of the functions may error. hvm-lang defines a builtin Result ADT which looks like this:

data Result = (Ok val) | (Err val)

This allows hvm-lang to do basic error handling. Right now, val is meant to be opaque; val's type is not stable and might change from one update to another.

HVM.log

Reads back its first argument and displays it on stdout, then returns its second argument.

main = (HVM.log "Hi" "Hello world")

// Program output:
"Hi"
"Hello world"

This function can be very useful for debugging, however, it's somewhat limited. For example, λx (HVM.log x 0) won't readback correctly

main = λx(HVM.log x "Hello world")

// Program output:
Reached Root
<Invalid>
λ* 0

This will happen whenever there are free variables inside the logged term, or scopeless lambdas which bind variables that are used outside the logged term.

HVM.print

This is very similar to HVM.log. However, it can only log strings. It prints these strings directly to stdout, without wrapping them in quotes or escaping them.

With HVM.log:

main = (HVM.log "Hi" "Hello world")

// Program output:
"Hi"
"Hello world"

However, with HVM.print:

main = (HVM.print "Hi" "Hello world")

// Program output:
Hi
"Hello world"

This can be very useful, for example, to print strings with newlines in multiple lines.

When the first argument is not a string HVM.print returns the second argument and no side effects are produced (In other words, it fails silently).

HVM.query

HVM.query reads a line from standard input, and calls its argument with a Result that might contain a string containing the user's input. It expects the standard input to be valid utf-8.

Here's an example program using HVM.query

Join (String.nil) x = x
Join (String.cons head tail) x = (String.cons head (Join tail x))
main = (((HVM.print "What's your name?") HVM.query) λresult match result {
	Result.ok: (HVM.print (Join "Hi, " (Join result.val "!")) *)
	Result.err: result.val
})

This program also shows using the return value of HVM.print (which is the identity function) to block HVM.query from reducing too early. If we used a naive version of the program, which is this:

main = (HVM.print "What's your name?" (HVM.query λresult match result {
	Result.ok: (HVM.print (Join "Hi, " (Join result.val "!")) *)
	Result.err: result.val
}))

We would get asked our name after typing it in.

HVM.store

HVM.store writes a string to a file, and calls its return value with a Result (the Result, if it's Result.ok, contains ERA).

Example:

main = (HVM.store "file.txt" "These are the file contents" λres match res {
	Result.ok: "Return value of program"
	Result.err: res.val
})

HVM.load

HVM.load, given a filename, reads the file and passes a Result that might contain the contents of the file to its argument

Join (String.nil) x = x
Join (String.cons head tail) x = (String.cons head (Join tail x))
main = (HVM.load "file.txt" λres match res {
	Result.ok: (HVM.print (Join "File contents: " res.val) "Program return value")
	Result.err: res.val
})

HVM.exit

HVM.exit terminates the processes with the status code determined by its argument.

main = (HVM.exit #42)
// Outputs nothing, but `hvml`'s status code will be 42.

On failure, it terminates the process with a -1 exit status code.

HVM.black_box

HVM.black_box is simply the identity function, but it does not get pre-reduced. This makes it possible to prevent some redexes from getting pre-reduced.

Example without HVM.black_box

foo = (* 30 40)
// Compilation output
@foo = #1200

Example with HVM.black-box

foo = (* (HVM.black_box 30) 40) // This is normal form
// Compilation output
@foo = a & @HVM.black_box ~ (#30 <* #40 a>)