What I'm talking about is modeling this aspect of Scheme's behavior:
(define x 5)
(define (f y) (+ x y))
(f 5) => 10
(set! x 10)
(f 5) => 15
Scheme's lambda evaluates its body during application, under the environment of its evaluation.Your LexLam evaluated its body with the right env, but in the wrong time: A subsequent mutation of env would not alter the result of application.
Conversely, DynLam evaluates its body at the right time (during application), but with the wrong env. Given this code:
(define (f y) (+ x y))
(let ((x 5)) (f 5)) => Error, x is unbound in f
Your DynLam would incorrectly evaluate to 10, right?I just don't see how to model this behavior without IORefs :-)
FWIW, I recognize your confusion as something I've had myself. I worked it out in Erlang, not Haskell, but it's the same principles. Again, ignoring threading, functional programming can do anything imperative programming can do with at most a log n penalty because at worst the functional program can simply simulate a raw expanse of memory in a tree and implement mutation on top of that. What you're asking about is ultimately much simpler than that but you may just have to work with it for a while. All I can really do is assure you that no, IORefs are completely unnecessary in this case.
And again let me emphasize my sympathies with your position and that I do not mean this harshly... I am recalling where I myself was in the past. Until I worked with it for long enough for it to click I'm not sure anyone really could have explained it to me. I'd been imperating for a long time.
http://jspha.com/posts/mutable_algorithms_in_immutable_langu...
It's all about controlling what evaluation occurs at what time. Here's another example: a language with dynamic and lexical bindings. You can see how the time when evaluation of the body of the lambda varies. I'm being very heavy-handed here, though—this only works for a pure language.