Refactoring with Currying
intermediateThe Problem
Look at the evaluate function you’ve built so far. Notice anything?
"l + r": (l, r) => evaluate(l) + evaluate(r),
"l - r": (l, r) => evaluate(l) - evaluate(r),
"l * r": (l, r) => evaluate(l) * evaluate(r),
"l / r": (l, r) => evaluate(l) / evaluate(r),
"l < r": (l, r) => evaluate(l) < evaluate(r),
"l > r": (l, r) => evaluate(l) > evaluate(r),
"l == r": (l, r) => evaluate(l) === evaluate(r),
"l != r": (l, r) => evaluate(l) !== evaluate(r),
Eight rules. All doing the same thing: evaluate both sides, apply an operator. The only difference is which operator. This kind of repetition is a signal — we’re missing an abstraction.
Operators are Just Functions
Think about what + actually does: it takes two values and produces a result. That’s exactly what a function does.
In JavaScript, you wouldn’t write eight separate if statements for each arithmetic operation. You’d write one function that takes an operator and two values. Or better — you’d realize + is already a function, just with special syntax.
The expression [2, "+", 3] is really just a function call in disguise: look up +, apply it to 2 and 3.
And there’s nothing special about the string "+" — it’s just a name, like "square" or "double". We chose to call our addition function "+", but we could have called it "add" or anything else. The only thing that makes it feel special is the infix syntax.
The Obstacle
There’s a catch. Our functions only take one argument:
["lambda", "x", body]
But operators need two. So how do we express + as a function?
Currying
The solution: a function that takes one argument and returns another function that takes the second.
In JavaScript:
const add = (x) => (y) => x + y;
add(2)(3); // -> 5
In Python:
def add(x):
def inner(y):
return x + y
return inner
add(2)(3) # -> 5
In our language:
["add", "=", ["lambda", "x", ["lambda", "y", ["x", "+", "y"]]]]
[["add", 2], 3] // -> 5
This technique is called currying — named after the logician Haskell Curry. It lets us express multi-argument functions using only single-argument ones.
The Payoff
Once we can curry, every operator is just a named curried function. The expression [2, "+", 3] becomes [["+", 2], 3] — look up +, apply it to 2, then apply the result to 3.
Eight repetitive rules collapse into one.
What You’ll Do
- First, you’ll upgrade the function call rule so it can handle curried calls like
[["add", 2], 3] - Then, you’ll replace all eight operator rules with a single rule that treats operators as function calls
Let’s clean up.