Foundations Expressions and Bindings Lesson 08

Bindings and control flow

Mental Model

Bindings are names available to an expression. Some bindings can be created locally with let; others are supplied by the host when it evaluates the expression.

let binds names sequentially:

(let [a 1
      b (+ a 2)
      c (* a b)]
  (+ a b c))

Open in playground →

Each binding can see earlier bindings in the same vector. The body can see them all.

if evaluates one branch:

(if (> t 0.5) 1 0)

Open in playground →

cond checks test/value pairs from left to right and stops at the first test that is truthy, returning its paired value. Tests and values after the chosen pair are not evaluated:

(cond
  (< x 0) -1
  (> x 0)  1
  true     0)

Open in playground →

Use a literal true as the default branch. If no test is truthy and there is no default, cond returns nil.

Worked Example

(group :name "fade"
  (shape :sdf
    :radius 0.5
    :alpha (lerp 1 0 (clamp t 0 1))))

Open in playground →

Here t is not declared inside the document. It is a binding the host supplies when evaluating :alpha, for example a normalized animation time.

Make the expression easier to scan with let:

(group :name "fade"
  (shape :sdf
    :radius 0.5
    :alpha (let [phase (clamp t 0 1)]
             (lerp 1 0 phase))))

Open in playground →

The expression is still small and local. If the logic stops feeling like “a little safe math”, lift it into the host domain and pass the result as data.

Exercises

Write an alpha fade:

(shape :sdf
  :alpha (lerp 0 1 (clamp t 0 1)))

Open in playground →

Now invert it:

(shape :sdf
  :alpha (lerp 1 0 (clamp t 0 1)))

Open in playground →

Use let to avoid repeating work:

(shape :sdf
  :radius (let [phase (clamp t 0 1)
                pulse (lerp 0.8 1.2 phase)]
            (* 20 pulse)))

Open in playground →

Repair the let shape:

(let [a 1 b] (+ a b))

Open in playground →

The binding vector must contain name/expression pairs. Give b a value:

(let [a 1
      b (+ a 2)]
  (+ a b))

Open in playground →

Repair the missing default:

(cond
  (< x 0) -1
  (> x 0)  1)

Open in playground →

This is valid, but returns nil when neither test matches. If a zero case is intended, add a default:

(cond
  (< x 0) -1
  (> x 0)  1
  true     0)

Open in playground →

Mastery Check

  1. Can a later let binding refer to an earlier one?

  2. Can an earlier let binding refer to a later one?

  3. What supplies t in an expression like (clamp t 0 1)?

  4. When should expression logic move out of SJON?