When ^? returns Nothing , it is often desired to know why.

Let's define a ^?? operator which returns an Either instead of a Maybe :

newtype ConstEither e r a = ConstEither { getConstEither :: Either e r } e r ae r } deriving Functor infixl 8 ^?? (^??) :: s -> LensLike' ( ConstEither e a) s a -> Either e a e a) s ae a ^?? f = f ( ConstEither . Right ) whole & getConstEither wholef () wholegetConstEither

The standard optics ( Traversal , Prism , etc) do not work with our new combinator, so let's see how we can define ones which would.

Traversal s t a b is forall f. Applicative f => (a -> f b) -> s -> f t and it uses f 's pure in the empty case, so we'll replace the Applicative with a verbose variant which supplies error information in the empty case:

class Apply f => VerboseApplicative e f where e f vpure :: e -> a -> f a f a type VerboseTraversal e s t a b = e s t a b forall f . VerboseApplicative e f => e f LensLike f s t a b f s t a b type VerbosePrism e s t a b = e s t a b forall p f . p f ( Choice p, VerboseApplicative e f) => p,e f) Optic p f s t a b p f s t a b type VerboseTraversal' e s a = VerboseTraversal e s s a a e s ae s s a a type VerbosePrism' e s a = VerbosePrism e s s a a e s ae s s a a -- Verbose optics support for (^.) and (^..) instance Monoid r => VerboseApplicative e ( Const r) where e (r) = pure vpure _ -- Verbose optics support for `preview`, aka (#) instance VerboseApplicative e Identity where = pure vpure _ -- Verbose optics support for our (^??) instance Apply ( ConstEither e r) where e r) ConstEither x <.> _ = ConstEither x instance e ~ e' => VerboseApplicative e ( ConstEither e' r) where e'e (e' r) = ConstEither ( Left e) vpure e _e)

Now we may want an operator to transform optics into verbose optics:

-- Given an error message constructor, turns: -- * Traversal to VerboseTraversal -- * Prism to VerbosePrism verbose :: ( Profunctor p, VerboseApplicative e f) => p,e f) -> e) -> (te) Optic p ( Lift f) s t a b -> p (f) s t a b Optic p f s t a b p f s t a b = verbose e t . t . rmap Other rmap frmap where Other r) = r f (r) Pure r) = vpure (e r) r f (r)vpure (e r) r -- A fixed variant of transformers:Control.Applicative.Lift - -- Turns an Apply to an Applicative -- (transformer's versions Applicative instance requires Applicative f) data Lift f a = Pure a | Other (f a) f a(f a) deriving Functor instance Apply f => Applicative ( Lift f) where f) pure = Pure Pure f <*> Pure x = Pure (f x) (f x) Pure f <*> Other x = Other (f <$> x) (fx) Other f <*> Pure x = Other (f <&> ( $ x)) (fx)) Other f <*> Other x = Other (liftF2 ( $ ) f x) (liftF2 () f x)

Note that I haven't found how to make verbose also turn a Fold to a verbose variant.

To see our verbose optics in action we'll make some verbose variants of optics from lens-aeson :

type Err = String v_Value :: ( AsValue t, Show t) => VerbosePrism' Err t Value t,t) = verbose (\x -> "Doesn't parse as JSON: " <> show x) _Value v_Valueverbose (\xx) _Value v_Double :: ( ToJSON t, AsNumber t) => VerbosePrism' Err t Double t,t) = verbose (expectJson "number" ) _Double v_Doubleverbose (expectJson) _Double vnth :: ( ToJSON t, AsValue t) => Int -> VerboseTraversal' Err t Value t,t) = verbose (expectJson ( "item at index " <> show i)) (nth i) vnth iverbose (expectJson (i)) (nth i) expectJson :: ToJSON a => String -> a -> Err = expectJson e x "Expected " <> e <> " but found " <> Data.ByteString.Lazy.Char8.unpack (encode x)

Now let's see they work:

# Verbose traversals can work like regular traversals > "[1, \"x\"]" ^? _Value . nth 0 . _Double _Valuenth_Double Just 1.0 > "[1, \"x\"]" ^? v_Value . vnth 0 . v_Double v_Valuevnthv_Double Just 1.0 > "[1, \"x\"]" ^? v_Value . vnth 1 . v_Double v_Valuevnthv_Double Nothing # But using ^?? rather than ^? we can also get error info > "[1, \"x\"]" ^?? v_Value . vnth 0 . v_Double v_Valuevnthv_Double Right 1.0 > "[1, \"x\"]" ^?? v_Value . vnth 1 . v_Double v_Valuevnthv_Double Left "Expected number but found \"x\"" > "[1, \"x\"]" ^?? v_Value . vnth 2 . v_Double v_Valuevnthv_Double Left "Expected item at index 2 but found [1,\"x\"]" > "hello" ^?? v_Value . v_Double v_Valuev_Double Left "Doesn't parse as JSON: \"hello\""

Notes

In the previous post's discussion, lens 's creator Edward Kmett noted that in lens 's early days they experimented with a different formulation of error-reporting optics that placed the extra information in Optic p f s t a b 's p rather than f , but that with that formulation they ran into problems with inference and that this new formulation may work better.

Request for feedback

Do you have use cases for this? If so, do you think that this should belong in lens ?

? Should it belong in a separate package? Perhaps along with the previous posts' Prism combinators and with additional optics like inverted Prism s and partial Iso s?

combinators and with additional optics like inverted s and partial s? Any code suggestions or improvements?

Discussion

Image source