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...
What I'm talking about is modeling this aspect of Scheme's behavior:
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:
Your DynLam would incorrectly evaluate to 10, right?I just don't see how to model this behavior without IORefs :-)