Skip to content

Troubleshooting

John Ky edited this page May 17, 2023 · 1 revision

Compiler errors given by oops can be a somewhat obscure. This page is to document those errors and how to respond to them.

Example 1

example1 :: ExceptT Int IO Int
example1 = OO.runOopsInExceptT @Int (OO.throw True)
------
src/Examples.hs:2:39: error:
    • Uh oh! I couldn't find Bool inside the variant!
      If you're pretty sure I'm wrong, perhaps the variant type is ambiguous;
      could you add some annotations?
    • In the second argument of ‘OO.runOopsInExceptT’, namely
        ‘(OO.throw True)’
      In the expression: OO.runOopsInExceptT @Int (OO.throw True)
      In an equation for ‘example1’:
          example1 = OO.runOopsInExceptT @Int (OO.throw True)
    |
334 | example1 = OO.runOopsInExceptT @Int (OO.throw True)
    |                                       ^^^^^^^^^^^^^

This means that the context of the expression (OO.throw True) does not have Bool in the variant's type list. You can find out what types are in the type list by placing a type hole (for example _u) in the expression's place. Doing so yields the following:

example1 :: ExceptT Int IO Int
example1 = OO.runOopsInExceptT @Int _u
-----
src/Examples.hs:2:38: error:
    • Found hole: _u :: ExceptT (Variant '[Int]) IO Int
      Or perhaps ‘_u’ is mis-spelled, or not in scope
    • In the second argument of ‘OO.runOopsInExceptT’, namely ‘_u’
      In the expression: OO.runOopsInExceptT @Int _u
      In an equation for ‘example1’:
          example1 = OO.runOopsInExceptT @Int _u
    • Relevant bindings include
        example1 :: ExceptT Int IO Int (bound at Examples.hs:2:1)
    |
334 | example1 = OO.runOopsInExceptT @Int _u
    |                                     ^^

We are trying to create a value of type Variant '[Int] from a value of type Bool which won't work.

In the context of error handling, this means the context does not handle errors of type Bool, but an error of type Bool is thrown. oops being a library for type-safe error handling does not allow code to have unhandled errors as that represents a bug in the code.

You can fix this error by:

  • making the context handle this error
  • handle this error here
  • modify the expression to not throw this error

Example 2

example2 :: ()
  => Monad m
  => CouldBe e Text
  => ExceptT (Variant e) m ()
example2 = OO.throw @Int 0
-----
src/Examples.hs:5:12: error:
    • Could not deduce (CouldBe e Int)
        arising from a use of ‘OO.throw’
      from the context: (Monad m, OO.CouldBeF e Text)
        bound by the type signature for:
                   example2 :: forall (m :: * -> *) (e :: [*]).
                               (Monad m, OO.CouldBeF e Text) =>
                               ExceptT (Variant e) m ()
        at src/Examples.hs:(5,1)-(5,29)
    • In the expression: OO.throw @Int 0
      In an equation for ‘example2’: _example = OO.throw @Int 0
    |
337 | example2 = OO.throw @Int 0
    |            ^^^^^^^^^^^^^^^

This means that the context of the expression (OO.throw True) does not have Bool in the variant's type list e. The type list e is parametric, so the way to specify the type list e contains a type is to use the CouldBe constraint.

The error already tells you what types are known to be in the e type list via the constraints that have been reported for example2.

The constraints list shows CouldBe e Text, so we know the typelist e contains Text but there is no CouldBe e Int.

In the context of error handling, this error means that the function example2 declares that it throws Text and nothing else, but internally it calls a function that throws Int without handling it.

You can fix this error by:

  • declaring examples2 also throws Int by adding CouldBe e Int to the constraint of the function
  • handle the Int error so that it doesn't propagate out of the function
  • modify the expression to not throw Int

Example 3

example3 :: ExceptT Int IO ()
example3 = OO.throw @Int 0
-----
src/Examples.hs:1:12: error:
    • Couldn't match type ‘Int’ with ‘OO.VariantF Identity e0’
        arising from a functional dependency between:
          constraint ‘MonadError (Variant e0) (ExceptT Int IO)’
            arising from a use of ‘OO.throw’
          instance ‘MonadError e (ExceptT e m)’ at <no location info>
    • In the expression: OO.throw @Int 0
      In an equation for ‘_example’: _example = OO.throw @Int 0
    |
334 | example3 = OO.throw @Int 0
    |            ^^^^^^^^^^^^^^^

Oops uses ExceptT (Variant e) to propagate errors. The type expression VariantF Identity e0 is equivalent to Variant e0.

This error is saying that Variant e and Int are not the same type. You get this error when trying to call code that uses oops for error handling from code that uses Vanilla ExceptT.

You can fix the error by using the runOopsInExceptT function like this:

example3 :: ExceptT Int IO ()
example3 = OO.runOopsInExceptT $ OO.throw @Int 0

To understand what's happening here you can replace the throwing expression with a type hole _u:

example3 :: ExceptT Int IO ()
example3 = OO.runOopsInExceptT _u
-----
src/Examples.hs:2:32: error:
    • Found hole: _u :: ExceptT (Variant '[Int]) IO ()
      Or perhaps ‘_u’ is mis-spelled, or not in scope
    • In the first argument of ‘OO.runOopsInExceptT’, namely ‘_u’
      In the expression: OO.runOopsInExceptT _u
      In an equation for ‘_example’: _example = OO.runOopsInExceptT _u
    • Relevant bindings include
        example3 :: ExceptT Int IO () (bound at src/Examples.hs:334:1)
    |
334 | example3 = OO.runOopsInExceptT _u
    |                                ^^

The type of _u is required to be ExceptT (Variant '[Int]) IO (). Notice that the Variant has the type list '[Int]. Expressions in located where the type hole is currently are allowed to throw Int and nothing else.

Example 4

data Error = Error1 Int | Error2 Bool

foo :: ()
  => e `CouldBe` Int
  => e `CouldBe` Bool
  => MonadError (Variant e) m
  => m ()
foo = OO.throw @Int 0

example4 :: ExceptT Error IO ()
example4 = OO.runOopsInExceptT foo
-----
src/Examples.hs:2:34: error:
    • Uh oh! I couldn't find Int inside the variant!
      If you're pretty sure I'm wrong, perhaps the variant type is ambiguous;
      could you add some annotations?
    • In the second argument of ‘($)’, namely ‘OO.throw @Int 0’
      In the expression: OO.runOopsInExceptT $ OO.throw @Int 0
      In an equation for ‘example4’:
          example4 = OO.runOopsInExceptT $ OO.throw @Int 0
    |
336 | example4 = OO.runOopsInExceptT $ OO.throw @Int 0
    |                                  ^^^^^^^^^^^^^^^

This error can happen when interfacing between calling oops code from non-oops code. Here we are correctly using runOopsInExceptT, however, the expression inside runOopsInExceptT may only throw Error. This is inferred from the context of the call to runOopsInExcept, which has the type ExceptT Error IO ().

Let's put in a type hole to find out:

data Error = Error1 Int | Error2 Bool

foo :: ()
  => e `CouldBe` Int
  => e `CouldBe` Bool
  => MonadError (Variant e) m
  => m ()
foo = OO.throw @Int 0

example4 :: ExceptT Error IO ()
example4 = OO.runOopsInExceptT _u

src/Examples.hs:11:32: error:
    • Found hole: _u :: ExceptT (Variant '[Error]) IO ()
      Or perhaps ‘_u’ is mis-spelled, or not in scope
    • In the first argument of ‘OO.runOopsInExceptT’, namely ‘_u’
      In the expression: OO.runOopsInExceptT _u
      In an equation for ‘example4’: _example = OO.runOopsInExceptT _u
    • Relevant bindings include
        example4 :: ExceptT Error IO () (bound at src/Examples.hs:11:1)
    |
336 | example4 = OO.runOopsInExceptT _u
    |                                ^^

This means we can only throw Error at that location but foo throws both Int and Bool.

That's okay. We can catch the Int and the Bool and wrap it in Error like this:

data Error = Error1 Int | Error2 Bool

foo :: ()
  => e `CouldBe` Int
  => e `CouldBe` Bool
  => MonadError (Variant e) m
  => m ()
foo = OO.throw @Int 0

example4 :: ExceptT Error IO ()
example4 = OO.runOopsInExceptT $ do
  foo
    & OO.catch @Int (OO.throw . Error1)
    & OO.catch @Bool (OO.throw . Error2)

Example 5

foo :: ExceptT Int IO ()
foo = pure ()

example5 :: ()
  => e `CouldBe` Int
  => ExceptT (Variant e) IO ()
example5 = foo
-----
src/Examples.hs:7:12: error:
    • Couldn't match type ‘Error’ with ‘OO.VariantF Identity e’
      Expected type: ExceptT (Variant e) IO ()
        Actual type: ExceptT Int IO ()
    • In the expression: foo
      In an equation for ‘_example’: _example = foo
    • Relevant bindings include
        example5 :: ExceptT (Variant e) IO ()
          (bound at src/Examples.hs:7:1)
    |
341 | example5 = foo
    |            ^^^

This is error is saying that Error and Variant e aren't the same type. This error can occur when call non-oops code from within oops code.

This can be solved by wrapping the call to foo in functions that convert the type ExceptT Int IO () to e `CouldBe` Int => ExceptT (Variant e) IO () like this:

foo :: ExceptT Int IO ()
foo = pure ()

example5 :: ()
  => e `CouldBe` Int
  => ExceptT (Variant e) IO ()
example5 =
  lift (runExceptT foo)
    & OO.onLeft OO.throw

The role of each function is as follows:

  • runExceptT: Converts ExceptT Int IO () to IO (Either Int ())
  • lift: Converts IO (Either Int ()) to e `CouldBe` Int => ExceptT (Variant e) IO (Either Int ()). Notice we are now in the same monadic context as example5. Now we just need to handle the Either Int in the return type of the monad.
  • onLeft: Extracts the Int from Either Int () leaving only (). onLeft takes a function which specifies what to do with the Int.
  • throw: Throws the Int as an error. Given that we are already in the correct monadic context that e `CouldBe` Int => ExceptT (Variant e) IO (), the throw and therefore the entire example5 function compiles.

As a general rule, to call non-oops code f from oops code g, you will need to take f and transform it such that all the errors are represented in the return type of the based monad that both f and g share then you lift to get the expression to be in the same monad as g, then you inspect the value for the errors embedded within and throw them.

Clone this wiki locally