lens-errors: Error handling in lens chains

[ bsd3, lenses, library ] [ Propose Tags ]

Please see the README on GitHub at https://github.com/ChrisPenner/lens-errors#readme

[Skip to Readme]
Versions [faq],,
Change log ChangeLog.md
Dependencies base (>=4.7 && <5), either, lens [details]
License BSD-3-Clause
Copyright Chris Penner
Author Chris Penner
Maintainer christopher.penner@gmail.com
Category Lenses
Home page https://github.com/ChrisPenner/lens-errors#readme
Bug tracker https://github.com/ChrisPenner/lens-errors/issues
Source repo head: git clone https://github.com/ChrisPenner/lens-errors
Uploaded by ChrisPenner at 2019-06-27T20:02:13Z
Distributions NixOS:
Downloads 932 total (6 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs uploaded by user
Build status unknown [no reports yet]


[Index] [Quick Jump]


Maintainer's Corner

For package maintainers and hackage trustees

Readme for lens-errors-

[back to package description]



Handling failure deep inside a lens chain intelligently is hard; You tried to crawl deep into a tree, but it failed and you're not sure why! This helps with that.

This library doesn't aim to serve every single use case; or provide a TON of error combinators, it mostly gives you the tools to write your own.


Here's what a usage could look like;

Let's say we have a nice tree:

let tree = Node 0 [Node 1 [Node 2 []]]

But we want to get the node at the bottom! No problem, we can write a traversal for that!

λ> tree ^.. branches . ix 0 . branches . ix 10 . root

Oh; we failed! We can probably guess in this example why the failure occured, but in larger more complex traversals it can be pretty tough to find sometimes; and since traversals branch out we might even fail in more than one place at a time!

lens-errors gives us a whole shwack of fizzle combinators which will allow us to effectively 'kill' a branch of the traversal with an error message while allowing other branches to continue to limp along and see if they can succeed.

Why fizzle you ask? Words like fail or throw seem to strong, in this case only certain branches of the traversal might fizzle out while allowing others to continue.

Let's build a new traversal which reports a nice error if we fail to index into the child branch we want:

let tryIx n = ix n `orFizzleWith` (\xs -> [show n <> " was out of bounds in list: " <> show xs])

Here we use orFizzleWith which will try to run the given traversal; but will build and throw a nice error if the traversal returns no elements.

Now that we've added errors to our lens chain the standard lens-running combinators like ^. and %~ won't work any more, but we've got new ones. Let's try ^&.. which runs a traversal and collects the elements into a list, with nice errors along the way!

>>> tree ^&.. branches . tryIx 0 . branches . tryIx 10 . root
(["10 was out of bounds in list: [Node {rootLabel = 2, subForest = []}]"],[])

What a great error message! Now we know exactly why we didn't manage to find any elements there.

You'll notice we actually returned a tuple of type ([String], [Int]); We return the errors in the left half and values in the right. Because of the branching factor of traversals, it's entirely possible for one branch to fail and another to succeed!

Let's see an example of that.

>>> [1, 2, 3, 4] ^&.. traversed . fizzleWithUnless (\n -> show n <> " wasn't even!") even
("1 wasn't even!3 wasn't even!", [2, 4])

Here we see two interesting things! One is that it returned some elements successfully! But also, it returned a funny looking error message; looks like our messages got smushed together!

lens-error lets you use any error type you want given you follow two conditions:

  1. All lenses in the chain must agree on the same error type
  2. The error type must be a Monoid.

The error combinators will end up mashing all your errors together, so usually it's nice to just wrap them all in a list; up to you though!

There are a few more fizzle combinators available; hopefully you can build what you need with them :)

For instance we can include failure messages on prisms.

If we use ^&?; We'll find the first successfull result; otherwise return all the errors we encountered.

>>> let prismError p name = p `orFizzleWith` (\v -> ["Value " <> show v <> " didn't match: " <> name])
>>> let _R = prismError _Right "_Right"

>>> [Left (1 :: Int), Left 2, Right (3 :: Int)] ^&? traversed . _R
Success 3
>>> [Left (1 :: Int), Left 2, Left 3] ^&? traversed . _R
Failure [ "Value Left 1 didn't match: _Right"
        , "Value Left 2 didn't match: _Right"
        , "Value Left 3 didn't match: _Right"]

More examples

For more examples you can reference the test suite.