The fun thing is that this sounds like "just" a more type-safe version of the way DSLs are usually implemented in imperative languages, particularly scripting languages.<p>In Python, if I have:<p><pre><code> def foo(x):
return x**2 - 1
</code></pre>
I can plug in a concrete argument:<p><pre><code> >>> foo(2)
3
</code></pre>
But I can also plug in abstract values provided by some library where all operators are overloaded to return another AST node. For instance:<p><pre><code> >>> from z3 import *
>>> expr = foo(Int('x'))
>>> expr
x**2 - 1
>>> type(expr)
<class 'z3.z3.ArithRef'>
</code></pre>
And then I can perform abstract operations on the resulting AST node, like solving for x:<p><pre><code> >>> solve(expr == 0)
[x = -1]
</code></pre>
Of course this is completely non-type-safe. And without purity, there's no guarantee that `foo(Int('x'))` is truly equivalent to running foo on an abstract value `x`. If there's an `if` statement in there, the condition will either pass or not, and the returned AST node will only include that control flow path. The DSL in question tries to block that by preventing AST nodes from being converted to boolean, but it's not perfect. Haskell has an advantage there.<p>On the other hand, for the situation at the end of the post, where the author wants to model imperative computations, it seems like an imperative language would make for easier embedding. In an imperative language, each AST node can be given its own unique ID when it's constructed. So there's no trouble distinguishing between, e.g., calling some function bar() and then using the return value twice, versus calling bar() twice. No need to use a separate <- operator for assignments.<p>But to be fair, explicitly distinguishing side effects is kind of the whole point of Haskell!<p>(And if you wanted badly enough to do it in Haskell without requiring <-, you could use unsafePerformIO to assign AST node IDs.)