Nothing, it is often desired to know why.
Let's define a
^?? operator which returns an
Either instead of a
The standard optics (
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
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 vpure :: e -> a -> f a type VerboseTraversal e s t a b = forall f. VerboseApplicative e f => LensLike f s t a b type VerbosePrism e s t a b = forall p f. (Choice p, VerboseApplicative e f) => Optic p f s t a b type VerboseTraversal' e s a = VerboseTraversal e s s a a type VerbosePrism' e s a = VerbosePrism e s s a a -- Verbose optics support for (^.) and (^..) instance Monoid r => VerboseApplicative e (Const r) where vpure _ = pure -- Verbose optics support for `preview`, aka (#) instance VerboseApplicative e Identity where vpure _ = pure -- Verbose optics support for our (^??) instance Apply (ConstEither e r) where ConstEither x <.> _ = ConstEither x instance e ~ e' => VerboseApplicative e (ConstEither e' r) where vpure e _ = ConstEither (Left 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) => (t -> e) -> Optic p (Lift f) s t a b -> Optic p f s t a b verbose e t = rmap f . t . rmap Other where f (Other r) = r f (Pure 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) deriving Functor instance Apply f => Applicative (Lift f) where pure = Pure Pure f <*> Pure x = Pure (f x) Pure f <*> Other x = Other (f <$> x) Other f <*> Pure x = Other (f <&> ($ x)) Other f <*> Other x = Other (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
type Err = String v_Value :: (AsValue t, Show t) => VerbosePrism' Err t Value v_Value = verbose (\x -> "Doesn't parse as JSON: " <> show x) _Value v_Double :: (ToJSON t, AsNumber t) => VerbosePrism' Err t Double v_Double = verbose (expectJson "number") _Double vnth :: (ToJSON t, AsValue t) => Int -> VerboseTraversal' Err t Value vnth i = verbose (expectJson ("item at index " <> show 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 Just 1.0 > "[1, \"x\"]" ^? v_Value . vnth 0 . v_Double Just 1.0 > "[1, \"x\"]" ^? v_Value . vnth 1 . v_Double Nothing # But using ^?? rather than ^? we can also get error info > "[1, \"x\"]" ^?? v_Value . vnth 0 . v_Double Right 1.0 > "[1, \"x\"]" ^?? v_Value . vnth 1 . v_Double Left "Expected number but found \"x\"" > "[1, \"x\"]" ^?? v_Value . vnth 2 . v_Double Left "Expected item at index 2 but found [1,\"x\"]" > "hello" ^?? v_Value . v_Double Left "Doesn't parse as JSON: \"hello\""
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
- Should it belong in a separate package? Perhaps along with the previous posts'
Prismcombinators and with additional optics like inverted
Prisms and partial
- Any code suggestions or improvements?