Quite a nice use case for cheap error locations! Works well for shallow embeddings.
Note that using error
to throw an exception is not a particularly safe thing to do, because GHC’s optimisations are free to mask it with another imprecise error or a loop.
The latter version where you do throw
should fare much better, because anything thrown through raiseIO#
is considered a precise exception and will inhibit strictness analysis, for example.
What’s more, I think I would prefer it if errors where part of Assembly
, i.e.
data Error = ...
data Assembly a
= MkAssembly (forall es. Stream Constant es -> Stream Error es -> Eff es a)
and then you yield errors in constant
or elsewhere. This has the advantage that you do not always need to capture the call stack, which might be expensive (if only in terms of memory residency).
Furthermore, I think it’s likely that one would want to differentiate between fatal errors (which halt the assembly pipeline immediately) and resumable errors (such as your “Wrong size constant”). Resumable errors can be yielded, but fatal errors would need to throw.