← Back to context

Comment by imdoor

8 hours ago

I think I much prefer Haskell DSLs over Lisp macros as the basis for APIs in foreign code. That might be due to my relative inexperience with Lisps, but macros just seem to make all the bad aspects of dynamically typed langues much worse. Looking at some piece of code in isolation, not only is it often impossible to tell what is the type/shape of data that are coming in (as is common with dynamic type systems) but with macros added to the mix I also can't tell what the control flow is. So to understand what a single piece of code is doing, I find myself chasing for hints that are scattered throughout the entire codebase.

Contrast this to Haskell's use of DSLs – although they really can be quite dense sometimes, I feel like, when I get stuck, I can always just dig into the documentation on Hackage, and figure out things from the type definitions (even when explanations in docs are lacking). Though it does require being comfortable with the abstractions being used (monads and such). Rust is similar in this manner but to a lesser extent.

But again, maybe the macro critique stems from my inexperience with them.

I wrote a couple macros that record data transiting through code at runtime (it's in Clojure, so basically almost every function is pure, returning what they produce as if it was water flowing out of a faucet), stores these intermediary results in a file, and finally display these values in the code itself, as comments, just below the call-site that produced them.

You can then, for a given call-site, choose to "load" these recorded computations, which will change the displayed comments, both below this call site and all the other instrumented call-sites that are downstream to it, even for code sitting in other source files.

It's a bit fragile and needs more polishing but it's a lot more convenient than any type system that will always get in the way, be not powerful enough, and it allows me to see what kind of data flows in my program without running it. Because I record everything and display the result not at compile-time but at coding time, in the same window, alongside the rest of the code. I don't understand why this was never done (to the best of my knowledge). Biggest limit I encounter is that Clojure doesn't provide any mean to identify areas in my code that are not pure.