I've come back on the idea of implementing more monadic error handling. I wasn't entirely convinced about the App
approach proposed with the introduction of QTT in idris2; it's not as composable as monad transformers and if I understand correctly it deals with errors in linear protocols by not allowing errors in linear protocols. On the other hand, I'm starting to think using monads is only hiding the problem behind abstractions rather than actually fixing it, and it's come to bite us in the butt with linearity.
To recap, the problem is that with the introduction of linear types, we can no longer use monad transformers to handle errors. The type (SomeError | b)
is a monad for regular b
's, but not if b
is linear. Linear values must always be consumers, but sum types add uncertainty: we do not know if the inner type of a monad even exists so how do we know if we can/must consume it?
Take a look at the following code:
vsRes <- createShaderFromSource GL_VERTEX_SHADER vertexSrc case vsRes of Err err => pure {m=Graphics} $ Err err Ok vs => LMonad.do fsRes <- createShaderFromSource GL_FRAGMENT_SHADER fragmentSrc case fsRes of Err err => LMonad.do () <- deleteShader vs pure {m=Graphics} $ Err err Ok fs => LMonad.do programC <- createProgram programC $ \program => LMonad.do program # vs <- attachShader program vs program # fs <- attachShader program fs program <- linkProgram program vs # program <- detachShader program vs fs # program <- detachShader program fs () <- deleteShader vs () <- deleteShader fs linkOk # program <- getProgramLinkStatus program if linkOk then pure {m=Graphics} (Ok (\f => f program)) else do log # program <- getProgramInfoLog program () <- deleteProgram program pure {m=Graphics} (Err $ MkLinkError log)
I would like to use a monadic style to make the happy-flow easier to read:
vs <- createShaderFromSource GL_VERTEX_SHADER vertexSrc fs <- createShaderFromSource GL_FRAGMENT_SHADER fragmentSrc programC <- createProgram programC $ \program => LMonad.do program # vs <- attachShader program vs program # fs <- attachShader program fs program <- linkProgram program vs # program <- detachShader program vs fs # program <- detachShader program fs () <- deleteShader vs () <- deleteShader fs program <- getProgramLinkStatus program |> handleLinkError pure {m=Graphics} $ Ok $ \f => f program
But this is wrong. The first snippet is correct. It checks for errors once, when they can appear, and handles them, including freeing resources. The second snippet unnecessarily puts the entire code in a more complex/permissive Kleisli category.
The fact that it isn't as readable isn't inherent to the program structure, but to how we chose to represent it as a single, unfolded, static string of characters. We could instead chose to display a single branch at a time. An IDE could display the first as:
vsRes <- createShaderFromSource GL_VERTEX_SHADER vertexSrc -- assume vsRes = Ok vs fsRes <- createShaderFromSource GL_FRAGMENT_SHADER fragmentSrc -- assume fsRes = Ok fs programC <- createProgram programC $ \program => LMonad.do program # vs <- attachShader program vs program # fs <- attachShader program fs program <- linkProgram program vs # program <- detachShader program vs fs # program <- detachShader program fs () <- deleteShader vs () <- deleteShader fs linkOk # program <- getProgramLinkStatus program -- assume linkOk pure {m=Graphics} (Ok (\f => f program))
Where we could "unfold" each assume
comment into a list of cases to change which branch is shown.
It further seems that Elm is doing great without any do notation and monadic error handling. It results in very nice code and seems quite beginner-friendly. The subject matter probably has a lot to do with it though. My WebGL API in particular is based around an inherently procedural specification, so some monadic style is probably beneficial... but I'm starting to doubt even that.
Since I'm no longer sure monad transformers for error handling are such a good idea (especially in the linear case), I'm postponing the decision on if/how to use them until the code base has gotten larger, so that I have a better grip on both the problem and Idris in general.
Top comments (0)