20
votes

Let's say I have a pair of conversion functions

string2int :: String -> Maybe Int
int2string :: Int -> String

I could represent these fairly easily using Optics.

stringIntPrism :: Prism String Int

However if I want to represent failure reason, I'd need to keep these as two separate functions.

string2int :: String -> Validation [ParseError] Int
int2string :: Int -> String`

For this simple example Maybe is perfectly fine, since we can always assume that a failure is a parse failure, thus we don't actually have to encode this using an Either or Validation type.

However imagine, in addition to my parsing Prism, I want to perform some validation

isOver18 :: Int -> Validation [AgeError] Int
isUnder55 :: Int -> Validation [AgeError] Int

It would be ideal to be able compose these things together, such that I could have

ageField = isUnder55 . isOver18 . string2Int :: ValidationPrism [e] String Int

This is fairly trivial to build by hand, however it seems like a common enough concept that there might be something lurking in the field of Lenses/Optics that does this already. Is there an existing abstraction that handles this?

tl;dr

Is there a standard way of implementing a partial lens / prism / iso that can be parameterised over an arbitrary functor instead of being tied directly to Maybe?.

I've used Haskell notation above since it's more straight forward, however I'm actually using Monocle in Scala to implement this. I would, however, be perfectly happy with an answer specific to i.e. ekmett's Lens library.

1
I remember a reddit discussion about a similar question from some way back. In it, Edward Kmett mentioned the notion of "coindexed prisms" that would be able to report error information while remaining composable with normal lenses. Apparently, they were difficult to fit into the lens framework because of type inference issues, so they weren't implemented.danidiaz
I think the concept of a Traversal would be appropriate here.Jon Schoning

1 Answers

6
votes

I have recently written a blog post about indexed optics; which explores a bit how we can do coindexed optics as well.

In short: Coindexed-optics are possible, but we have yet to do some further research there. Especially, because if we try to translate that approach into lens encoding of lenses (from Profunctor to VL) it gets even more hairy (but I think we can get away with only 7 type-variables).

And we cannot really do this without altering how indexed optics are currently encoded in lens. So for now, you'll better to use validation specific libraries.

To give a hint of the difficulties: When we try to compose with Traversals, should we have

-- like `over` but also return an errors for elements not matched
validatedOver :: CoindexedOptic' s a -> (a -> a) -> s -> (ValidationErrors, s)

or something else? If we could only compose Coindexed Prisms their value won't justify their complexity; they won't "fit" into optics framework.