My post about merging maps got me thinking about merging lists and sets.
List.zipWith lead to plenty of sneaky bugs, just like
Map.union. Again, we could write our own function each time using primitive recursion, but it happens enough that it'd be handy to be able to do it inline without thinking too much.
This post will focus on the contents of Hackage's
these, which is maintained by C McCann. I was struggling to understand some of the library's concepts, which are presented in a quite abstract setting, so I'll aim to keep it somewhat concrete, so it can be used as a crummy quick-start tutorial by future lazyfolk.
The namesake of the package,
These , is quite similar to
data These a b = This a | That b | These a b
Left a is
Right b is
That b, and we get a new constructor for when we have both,
These a b.
Functor instance only lets us touch the
λ> import Data.These λ> fmap not (This 'a') This 'a' λ> fmap not (That True) That False λ> fmap not (These 5 False) These 5 True
Bifunctor instance is a little more useful:
λ> bimap Char.ord not (These 'a' False) These 97 True
Applicative instances for
These a b requires a
Semigroup constraint on
a. It does its best to retain every
a until it encounters a
This, when it has to stop for reasons similar to my accumulating errors post:
λ> (,,) <$> That 5 <*> These ["warning!"] 42 <*> This ["error!"] This ["warning!", "error!"] λ> (,,) <$> This ["error!"] <*> That 5 <*> These ["warning!"] 42 This ["error!"]
Applicative instance could easily continue collecting every
This, but that would make
ap different to
(<*>), likely offending lambda man or whoever.
For this reason, the docs describe it as "a hybrid error/writer monad, as would be expected." I suppose someone expected this, one of those mythical folk who think before they type! Not I, I had to ham around in GHCi and write about it.
These is a pretty useful type, somewhere between
Writer, encapsulating things I do all the time: making mistakes, contriving values, and ignoring mistakes while pontificating. If it were in
base, I'd probably use it all the time. Nevertheless, it's not in my dependency tree yet. Before I can bring myself to introduce it to a project, I require a little more value from the package.
These sliced four ways, concisely solving moderately-practical problems:
From the README:
The type class Align is here because f (These a b) is the natural result type of a generic "zip with padding" operation--i.e. a structural union rather than intersection.
Here we go: safe zipping.
Align class requires us to implement two functions:
nil :: f a, representing an empty structure
align :: f a -> f b -> f (These a b), which is pretty close to
zip, if you squint. Given two values for some alignable functor, produce the
Thesecombination for those two values.
Maybe instance is not all that interesting:
λ> align (Just 10) Nothing Just (This 10) λ> align (Just 10) (Just True) Just (These 10 True) λ> align Nothing (Just True) Just (That True)
The instance for lists is pretty straightforward, and what we probably want most of the time:
λ> nil :: [Bool]  λ> align [1..3] [1.0 .. 6] [These 1 1.0,These 2 2.0,These 3 3.0,That 4.0,That 5.0,That 6.0]
Seq instances behaving pretty similarly.
The best bang for buck comes from the wonderfully-named
malign (and its
salign), which handles the
zipWith (<>) case (while handling remnants correctly on either side):
λ> :t malign malign :: (Monoid a, Align f) => f a -> f a -> f a λ> :t salign salign :: (Data.Semigroup.Semigroup a, Align f) => f a -> f a -> f a λ> malign ["foo", "bar", "baz"] ["quux"] ["fooquux","bar","baz"]
padZipWith is also quite handy, allowing us to get safe padded
zipWith without even importing
λ> :t padZipWith padZipWith :: Align f => (Maybe a -> Maybe b -> c) -> f a -> f b -> f c λ> padZipWith (\ a b -> (+) <$> a <*> fmap fromRational b) [1..5] [1.0 .. 3] [Just 2.0,Just 4.0,Just 6.0,Nothing,Nothing]
... I suspect it is not particularly helpful when using the
Unfortunately, there's no
Align instance for
Map, because they demand an
Ord constraint. There's an instance for
IntMap and an instance for
Seq, which suggests we could probably write them if not for the additonal constraints. We don't really need
Map, since we have
Map.merge, but it would have been quite helpful for
See Sculthorpe et al, The Constrainted-Monad Problem to tour the painful intersection of constraints and typeclasses.
Crosswalk is to Align as Traversable is to Applicative. That's all there is to say on the matter.
This class motivated me to write this ridiculous post. I read that quote and still did not quite understand what
Crosswalk was. Now I'm here to confuse you with some helldamned GHCi session dumps.
The List instance lets us align all the things at once, rather than two at a time:
λ> :t sequenceL sequenceL :: (Crosswalk t, Align f) => t (f a) -> f (t a) λ> sequenceL [[1..5], [6..10], [11..12]] [[1,6,11],[2,7,12],[3,8],[4,9],[5,10]]
The instance for
Maybe is not so useful:
λ> sequenceL (Just [1..5]) [Just 1,Just 2,Just 3,Just 4,Just 5]
... and the instance for
These is quite unintuitive, especially if you've just used the list instance:
λ> sequenceL (These [1..3] [4.0 .. 8]) [These [1,2,3] 4.0,These [1,2,3] 5.0,These [1,2,3] 6.0,These [1,2,3] 7.0,These [1,2,3] 8.0]
These does not have an
Align instance; it is being used here via its
Functor, so we can't do anything useful with the
Unfortunately it is too perfect an abstraction to be useful. - elliott
Crosswalk for bifunctors. If you're not into bifunctors, they are much like Functor, but of higher kind; in practice, this means we can map over two type parameters instead of just the one.
At the time of writing there were but two
Bicrosswalk instances. Too perfect, indeed!
Either instance means well, and is very friendly, but does not help us a lot:
λ> :t bisequenceL bisequenceL :: (Bicrosswalk t, Align f) => t (f a) (f b) -> f (t a b)
λ> bisequenceL (Left [1..5]) [Left 1,Left 2,Left 3,Left 4,Left 5] λ> bisequenceL (Right [1..5]) [Right 1,Right 2,Right 3,Right 4,Right 5]
... and the instance for
These does what I expected its
Crosswalk to do, aligning both values as best it can:
λ> bisequenceL (This [1..5]) [This 1,This 2,This 3,This 4,This 5] λ> bisequenceL (That [1..5]) [That 1,That 2,That 3,That 4,That 5] λ> bisequenceL (These [1..4] [5..7]) [These 1 5,These 2 6,These 3 7,This 4]
I can foresee the
These instance being useful to someone, somewhere.
The package also includes
Control.Monad.Chronicle , containing a mtl-style monad transformer with the hybrid error/writer functionality of
I'm out of energy for this post and I haven't used it before. I note it has renamed versions of most of
Writer's key functions, plus equivalents to
catchError. Looks useful, and could replace some of the awful things I do with
You probably want to check for space leaks before running off and using it, since it resembles
WriterT, the famous leaky monad.
I'd like to thank the maintainers of
these for providing lenses while avoiding a
Many of us choose not to use lenses or to use competing libraries, to avoid unnecessary constraints and upstream risk. It is a joy to discover an interesting new package that can be used without a moment's hesitation. Thank you.