Domain errors with HasCallStack

Here’s my latest article, about how to use HasCallStack for error messages in embedded domain specific languages.

7 Likes

This feels unnatural.

The constant function should have a “position” argument, and for this particular case the position is getCallstack. Exceptions are not needed here at all (though I could go as far as to say they’re not needed ever when dealing with expected errors, but that’s just my view on things).

Also I would expect the size check on Data to be performed in a separate stage instead of being mushed together with all other future errors.

Sure, all those suggestions are fine. This is just a small sketch to present the idea of even carrying around CallStacks at all in EDSLs.

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.

1 Like

Yeah, agreed that imprecise exceptions are not the correct approach. To clarify for those less familiar with the area, “not particularly safe” here doesn’t mean unsafe like unsafeCoerce or unsafePerformIO, but it does mean it can be uncertain which exception is raised within pure code, for example error "foo" + error "bar" is not guaranteed to throw one or the other.

1 Like