4 ms·
One key difference between this and the IO monad or a free monad: There's no continuation. That is, you can't communicate between the code that produces the co
by brandonbloom 11y ago
One key difference between this and the IO monad or a free monad: There's no continuation.
That is, you can't communicate between the code that produces the command sequence and the code that interprets it. This is either a shortcoming, or a dramatic simplification, depending on your needs. I find that it's often the latter, since it enforces a one-way information flow.
- phamilton 11y agoI suppose that would be the "monad" part.
- daxfohl 11y agoI looked at this, and isn't it inside-out though? Given that code/data equivalence being the fundamental thing that makes a lisp a lisp, wouldn't the lisp way to go about it be, to write a whole-program-transformation macro that can replace certain function calls with specified equivalents? That way you can write code just like normal, but have it do completely different things based on the context. The idea of being able to do this is what made me consider a lisp in the first place. Yet I don't see much evidence of people working in that direction.
- brandonbloom 11y agoThere's a lot of stuff packed in to your comment here. The code/data equivalence isn't necessary to make this data-first approach work. Consider JavaScript, where it's quite popular to power complex processes with simple JSON (maybe + extensions, like functions). The approach also works well in typed functional languages, albeit with greater declaration burden. Moreover, it would even work well in languages like Java, if you're willing to buck the XML trend. That said, data being a subset of code (which is also implied by equivalence) is what makes this technique much more popular in Lisps (and JavaScript). Being able to easily move forms between evaluation, quoting, interpretation, etc is critical to evolving the right approach. Regarding whole program transforms: They don't compose, at least not without a substantially more sophisticated macro system (like Racket's) or evaluator (see work on "extensible interpreters"). Check out Racket's rich literature on macros if you want to see the state of the art here. Lastly, changing behavior based on context can be accomplished by many techniques including interpretation, dynamically scoped binding, etc. Read about Common Lisp's condition system, or Eff's "Algebraic Effects and Handlers" to see the state of the art of the dynamic substitution of effects. However, each new context needs to carefully reason about the interleaving of effects, since time is linear. If you want more flexibility with time, you have to turn it in to space. That's what happens when you take a step-by-step procedure and turn it in to data you can interpret. Consider code that prints a lot of stuff. Instead, you can return a lazy sequence of strings and then at the very end do something like `(apply str parts)` to concatenate it all together. Now that each sub-printer doesn't have side effects, you can re-order and refactor the calls without worrying about messing up the printed output. You've decoupled the evaluation timeline from the linear order of the output.