In the [first Haskell Cast podcast](http://www.haskellcast.com/episode/001-edward-kmett-on-lenses/) Rein Henrichs and Chris Forno interviewed Edward Kmett in part about `lens` and it was suggested that `Prism`s don't have the same kind of introductory tutorial treatment. That's a shame, though. `Prism`s arise naturally all the time when using sum types. ## You could have invented Data.Aeson.Lens (and maybe you already have) I learned to use `Prism`s when using [`lens`](http://hackage.haskell.org/package/lens) [1] with [`aeson`](http://hackage.haskell.org/package/aeson) so let us examine an extended use case to motivate using `Prism`s. It's fairly common in dynamic languages to consume JSON like this ```python > it = json.loads('[{"someObject" : { "version" : [1, 0, 0] }}]') > it[0]["someObject"]["version"][1] ``` which might, say, be accessing the minor version number of some nested object. In Python here we use an isomorphism (i.e. a two-way mapping that establishes equivalence) between JSON types and fairly common Python types and then just destruct the JSON information as an "array of dictionaries of dictionaries of arrays". Python users translating that line to Haskell with the JSON library [`aeson`](http://hackage.haskell.org/package/aeson) can grow frustrated by the (pathological) example below showing how tedious the manual destructuring in Haskell is when you're forced to be explicit but don't know how to use `Lens`s or `Prism`s. ```haskell import Prelude hiding (lookup) import Data.HashMap.Strict (lookup) import Data.Vector ((!?)) case decode "[{\"someObject\" : { \"version\" : [1, 0, 0] }}]" of Nothing -> error "Oh, I guess sometimes the JSON string might be invalid..." Just (Array a) -> case a !? 0 of Nothing -> error "Oh, previously I assumed there was at least ONE object..." Just (Object o) -> case lookup "someObject" o of ... Just _ -> error "Oh, what about if I don't actually have an object?" Just _ -> error "Oh, what about if I don't actually have an array?" ``` Destructuring is *far* more explicit here and conflates getting the values with all kinds of "schema validation" errors. Usually the next step is to learn to use the `ToJSON`/`FromJSON` machinery to fold all of your schema validation into `decode` and then build a really robust, independent serialization adapter for whatever internal ADTs you're using to actually model your domain. Which is great when you've got time to kill, but it doesn't really represent that original Python snippet which is far more likely to be a one-off JSON-munging script than a robust program. So can we do better if we just want to either get our value or fail? ### Failure is a monad right? [Of](http://hackage.haskell.org/package/errors) [course](http://hackage.haskell.org/package/either) [it](http://hackage.haskell.org/package/MaybeT) is. Let's pick `Maybe` and inject everything into it to consolidate those errors. We'll need to extract a few helpers from the above ```haskell anObject :: Value -> Maybe Object anObject (Object m) = Just m anObject _ = Nothing anArray :: Value -> Maybe Array anArray (Array a) = Just a anArray _ = Nothing ``` and then using `do` notation the example is much clearer. ```haskell do json <- decode thatString ary <- anArray json zeroth <- ary !? 0 obj <- anObject zeroth keys <- lookup "someObject" obj keyObj <- anObject keys ver <- lookup "version" key Obj verAry <- anArray ver verAry !? 1 ``` which is much nicer and, for those with a touch of Monad-fu, can be further simplified by using `(>=>)` into something that's *almost* as nice as the original Python code while being far more explicit about the kinds of assumptions and failure modes that exist. ```haskell decode >=> anArray >=> (!? 0) >=> anObject >=> lookup "someObject" >=> anObject >=> lookup "version" >=> anArray >=> (!? 1) ``` One way to think of this chain is it's a method of traversing our JSON structure by repeated descent into sum types like `Value`. Getting a `Nothing` indicates that the descent we would have liked to take is actually not available. With all this talk of "repeated descent" and "traversing" you can begin to expect that there might be a way to achieve this from within the `lens` ecosystem. Basic `Lens`es don't quite work because they don't have a good notion of failure, but there is a straightforward and powerful way to encode this into `lens` and we'll see that it's essentially exactly what is done in `Data.Aeson.Lens`. ## A short diversion on the nature of Traversals A Lens can usually be thought of as a first-class method of separating a piece of an object from its whole. In this light, we can think of `_1`, suitably specialized, as the split ``` (a, b) --> a AND ( _ , b ) ``` which then lets us operate by either retreiving the piece or updating the whole. An obvious generalization is the `Traversal` which lets us generalize our target to 0-or-more parts at once. This takes shape in `both :: Traversal' (a, a) a` which targets both the `fst` and `snd` element simultaneously or `each :: Traversal' [a] a` which targets all of the elements of a list at once. `Traversal`s are much more powerful than `Lens`es but, as usual, with more power comes less certainty. `Lens`es can be `view`ed or `(^.)`'d easily since they embody the guarantee that we're focused on exactly one part of the whole. `Traversal`s on the other hand can target subparts which may not exist. Consider what would happen if you tried to `view each` of a list. If `each` were a lens then `view elements` would have type `[a] -> a` but there are two things that can go wrong: 1. If the list is empty we'll have to throw an error 2. If the list has multiple elements we'll have to "summarize" them somehow. In order to implement `view` for a traversal, we'll need support from a `Monoid` instance for `a` so that we can handle case (1) with `mempty` and case (2) with `mappend`. This is why you can sometimes get weird errors in lens such as what happens when we try `view each "foo"` ```haskell :42:22: No instance for (Data.Monoid.Monoid Char) arising from a use of `each' ``` as `view` is trying to combine the multiple subparts with a non-existent `Monoid` instance. If we try it instead like `view each ["f", "o", "o"]` we can use the (free) `Monoid` instance for `[a]` ```haskell > view each ["f", "o", "o"] "foo" ``` For convenience, `lens` gives us two variants on `view`/`(^.)` which pre-wrap our subparts in useful monoids. We have `preview`/`(^?)` which prewraps the subparts in `First` and `toListOf`/`(^..)` which prewraps the subparts in `[]`. ```haskell > "foo" ^? each Just 'f' > "foo" ^.. each "foo" ``` We'll make use of `(^?)` especially in the parts to come. ## Traversing JSON with Prisms Remember that the trouble with using `Lens`es to peel off layers of our JSON structure is that our expectations about whether or not a layer can actually be peeled away may be incorrect and `Lens`es assume that we have exactly one valid subpart to target. More generally, you might say that `Lens`es target product types well, picking one of several pieces to focus on, but have trouble with sums (coproducts) since we may not have any pieces at all for them to target. `Traversal`s solve this problem by using a `Monoid` instance to handle failure and multiple results well, but they do so at the cost of knowing anything at all about how many pieces are being targeted. This is sufficient for JSON, but a little bit of overkill. We know that there will be exactly 0 or 1 `Array`s in any `Value`. That notion is the first part of a `Prism`. The [haddocks](http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-Prism.html) say that a `Prism` is a Traversal that can also be turned around with re to obtain a Getter in the opposite direction. It turns out that "turning around" is a very powerful property that lets us rebuild entire structures from only their piece and the knowledge that they ought to be accessible via our prismatic `Traversal`. To drive the distinction home, consider a `Traversal` over the 3rd element in a list (it's called `ix 2`). It's 0-or-1 targeted, but given only a piece of the list and that `Traversal` you cannot rebuild the origin list uniquely. The Traversal laws hold for every Prism and we traverse at most 1 element. 0-or-1 target `Traversal`s that can be ["Reviewed"](http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-Review.html) like this form exactly the `Prism`s. For instance, `lens` has an entire module and class devoted to [`Cons`-ing things on to the front of lists](http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-Cons.html)—an operation that, unlike `ix 2` can be "reviewed". The place you typically will see `Prism`s is when desconstructing a sum type. Each disjoint constructor will get its own `Prism` as any value of the sum has either 0 or 1 of those constructors being used. This is exactly the notion we need to "lensify" the `aeson` `Value` type, and, indeed, that's exactly what `Data.Aeson.Lens` from `lens` provides. ```haskell _Object :: Prism' Value Object _Object = prism' anObject Object _Array :: Prism' Value Array _Array = prism' anArray Array _String :: Prism' Value String _String = prism' aString String _Number :: Prism' Value Number _Number = prism' aNumber Number ``` Which is not much more than an introduction of our "failing deconstructors" from the first section. `Data.Aeson.Lens` also provides a few `Traversal`s based upon [`ix`](http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-At.html) but specialized to JSON types ```haskell key :: Text -> Traversal' Object Value nth :: Int -> Traversal' Array Value ``` Using these `Prism`s and `Traversal`s we can pick apart our JSON object as we please. ```haskell case decode theString of Nothing -> Nothing Just it -> it ^? _Array . nth 0 . _Object . key "someObject" . _Object . key "version" . _Array . nth 1 ``` Or, even better, by using the built-in `Data.Aeson.Lens` isomorphisms and type classes we can write this directly. ```haskell theString ^? nth 0 . key "someObject" . key "version" . nth 1 ``` and have the 1-or-0 target nature of `Prism`s work its magic in the background. Finally, we're looking at a Haskell example which rivals the original Python's terseness without sacrificing nearly as much type safety. Want to play with the example? Here's a bit of preamble to get you going in ghci. ```haskell -- to play with this example from ghci, -- install lens 4 or newer and :set -XOverloadedStrings -- OverloadedStrings is so it can automatically turn the String literals into Text -- values which is the type used for indexing into Object. import Data.Aeson import Data.Aeson.Lens import Control.Lens -- this code is intended for ghci experimentation, so we use "let" to bind a value to a name let jsonBlob = "[{\"someObject\": {\"version\": [1, 0, 3]}}]" -- example from above let myVal = jsonBlob ^? nth 0 . key "someObject" . key "version" . nth 1 -- What progressively composing the prisms looks like, note the composition order: -- λ> jsonBlob ^? nth 0 Just (Object fromList [("someObject", Object fromList [("version",Array (fromList [Number 1.0, Number 0.0, Number 3.0]))])]) -- λ> jsonBlob ^? nth 0 . key "someObject" Just (Object fromList [("version", Array (fromList [Number 1.0, Number 0.0, Number 3.0]))]) -- λ> jsonBlob ^? nth 0 . key "someObject" . key "version" Just (Array (fromList [Number 1.0, Number 0.0, Number 3.0])) -- λ> jsonBlob ^? nth 0 . key "someObject" . key "version" . nth 1 Just (Number 0.0) ``` --- (Thanks for commentary and corrections: acow, Effnote, edwardk, shachaf. Thanks for updating this to be current for the `lens-4.*` series to Chris Allen (@bitemyapp)) [Comments on Reddit](http://www.reddit.com/r/haskell/comments/1l64oe/lensaeson_traversalprism/) [1] Note that that *isn't* [`aeson-lens`](http://hackage.haskell.org/package/aeson-lens), which is a similar package that looks like a bunch of `Lens`es but is actually invalid, in no small part because it doesn't invoke any `Prism`s. Also, `lens-aeson` has been deprecated and its support for Aeson has been merged into the main `lens` library.