3 - Deeper into CLVM

This guide directly continues on from part 1 so if you haven't read that, please do so before reading this.

This section of the guide will cover how ChiaLisp relates to transactions and coins on the Chia network. If there are any terms that you aren't sure of, be sure to check the glossary.

Lazy Evaluation in ChiaLisp#

As we saw in part 1, programs are often structured around (i A B C) to control flow. ChiaLisp evaluates programs as trees, where the leaves are evaluated first. This can cause unexpected problems if you are not aware of it. Consider the following program which uses x which immediately halts and throws an error if it is evaluated.

$ brun '(i (q 1) (q 100) (x (q "still being evaluated")))'
FAIL: clvm raise (0x7374696c6c206265696e67206576616c7561746564)

This is because ChiaLisp evaluates both of the leaves even though it will only follow the path of one.

To get around this we can use the following design pattern to replace (i A B C).

((c (i (A) (q B) (q C)) (a)))

Applying this to our above example looks like this:

$ brun '((c (i (q 1) (q (q 100)) (q (x (q "still being evaluated")))) (a)))'

It is worth keeping this in mind whenever you write an (i A B C).

If you're wondering how this works (and how the standard transaction from part 2 worked), then allow me to introduce Eval.

Introduction to Eval#

In Part 1 we mentioned that a program is usually a list where the first element is an operator, and every subsequent element is a valid program. However a Program can also have a program as the first element. This will cause that program to be evaluated as a new puzzle. The solution is then every element after the first in this list.

This looks like this:

(*(puzzle)* *solution_element_1* *solution_element_2* *solution_element_3* *solution_element_4*)

In order to create this list we want to use:

((c *(puzzle)* *(solution)*))

Let's put this into practice.

Here is a program that evaluates the program (+ 2 (q 5))) and uses the list (70 80 90) or (80 90 100) as the solution.

$ brun '((c (q (+ 2 (q 5))) (q (70 80 90))))' '(20 30 40)'
$ brun '((c (q (+ 2 (q 5))) (q (80 90 100))))' '(20 30 40)'

Notice how the original solution (20 30 40) does not matter for the new evaluation environment. In this example we use q to quote both the new puzzle and the new solution to prevent them from being prematurely evaluated.

A neat trick that we can pull is that we can define the new solution in terms of the outer solution. In this next example we will add the first element of the old solution to our new solution.

$ brun '((c (q (+ 2 (q 5))) (c 2 (q (70 80 90)))))' '(20 30 40)'

However it's not just the new solution that we can affect using this, we can also pass programs as parameters.

Programs as Parameters#

The core CLVM does not have an operator for creating user defined functions. It does, however, allow programs to be passed as parameters, which can be used for similar results.

Here is a puzzle that executes the program contained in (f (a)) with the solution (12).

$ brun '((c 2 (q (12))))' '((* 2 (q 2)))'

Taking this further we can make the puzzle run a new evaluation that only uses parameters from its old solution:

$ brun '((c 2 1))' '((* 5 (q 2))) 10)'

We can use this technique to implement recursive programs.