So, over the last couple of days, I hacked together a roc implementation of the monkey interpreter from interpreterbook.com called 🐵🤘🏼 (monkey roc!). As I was working on functions/closures in the language, I realized that how they define environments is really hard to do in Roc. Was wondering if anyone has some tips on how to do this in general in a pure functional languages. The fundamental issue is that function in the monkey language hold a mutable reference to their definition environment. When executed, they create a wrapper environment built on top of their definition environment. Here are some concrete examples:
# Currently in env 0: global env
# On definition retX grabs a mutable references to the current env, env 0
let retX = fn() { return x; };
let x = 3;
# retX runs creating env 1 that just wraps env 0.
# When looking up x, it falls back on env 0 for the definition and returns 3.
retX();
let x = 12;
# This now returns 12. Since it mutably refers to env 0.
retX();
# Currently env 0
# Add definition binds to env 0
let add = fn(x) {
fn(y) { x + y; };
};
# Calling creates env 1 that wraps env 0
# In env 1, x is set to 2.
# This returns the lambda: fn(y) { x + y; }; that binds to env 1
let add2 = add(2);
# Calling creates env 2 that wraps env 1
# In env 2, y is set to 2.
# This looks up x in env 1 and y in env 2 to return 4.
add2(2);
In a pure functional language like roc, I don't know of a good way to mutably bind to a recursive chain of environments. Obviously, the default behavior of roc if you use a recursive tag would be to grab an immutable reference to the chain of environments. As such, it won't update upon mutation.
My currently solution is to have a giant list of environments and make references indices into that array. This works, but means that roc can't automatically clean up old environments for me. Instead I would need to either manually refcount the environments or do some sort of other garbage collection to avoid keeping around tons of dead environments. Any general thoughts.
Messy, hacked together code for evaluation all lives here: https://github.com/bhansconnect/monkey-roc/blob/main/src/Eval.roc
Last updated: Jul 05 2025 at 12:14 UTC