AFAIK, this is a hallmark feature of LISP dialects: For example, suppose we launch an ongoing LISP program that makes repeated calls to some function named func
. We are then able to go inside the lisp REPL, change the definition of func
, and then the next time the program makes a call to func
, the behavior of the program will be appropriately changed live.
I believe this property is called late binding, although I'm not sure if I'm correct.
The benefit of late binding is that it allows you refactor or even completely change a program while it is still running. This іs analogous in biology to the fact that, as we age, almost every cell in our body gets replaced by a new one over a long enough time horizon (but of course we never notice). It can make systems incredibly flexible.
Question: Is there any way to do something analogously in Haskell? If Haskell doesn't allow this, is there a good reason for it to (i.e., is it making some greater trade-off that makes it all worth it)?
A good way to understand declarative programming is to think of it as being timeless. So there is no "the next time the program makes a call to func
". If the program behaved different the "next time" in some low-level, operational sense, this would be a violation of purity. Keeping our FP colored lenses firmly in place as we step outside of the program, we note that you can't "change the definition of func
", instead what you are doing is making a new program with a different definition of func
. So really what you are talking about doing when you "change the definition of func
" is abandoning the current program and running a new one. There are Haskell libraries that work just this way such as halive and dyre.
The IO
monad does let us model a "before" and "after", so within it, we can talk about "changing" something. Changing "the" program still has the same concerns as above, but we can certainly have a reference cell that holds a bunch of code that we update. Effectively, this is what's happening in (naive implementations of) Lisp. You can actually do this manually, e.g. define an IORef
or MVar
holding the function you want to change and updating it as appropriate. Now the problem is you usually want to update to brand new functions, so we need to either have a way to describe new functions or to load functions on the fly. The former corresponds to having an interpreter available which nominally is what hint and mueval do (though they actually work more like the following). Doing the latter is dynamic code loading and libraries like plugins, rts-loader, and dynamic-loader do this.
Alternatively, you can take the view (or actually structure it so) that parts of your code are running as a coprocess. At this point, you can use standard IPC mechanisms. "Calling a function" in this context would merely mean sending a message.
Ultimately, none of these provide an experience like a Lisp. The Haskell language itself provides no mechanism or notion of a "place" where a definition is "stored" that could then be updated. Again, conceptually in a Lisp, every definition is stored in a mutable cell and every function call first dereferences that mutable cell to get the current definition. The upshot is every library and technique I've mentioned requires you to plan ahead of time where the change points are or what you want to reload or, at the very least, that you want to reload. There is no notion of "attaching" to a running Haskell process or "breaking" into a debugger. Technically, though, with the technique dyre uses you could relaunch into an executable written in a totally different language, let alone an arbitrarily changed Haskell executable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With