Two well-known examples of applicatives are monads and ziplists. Are there any other examples?
I recently defined an Applicative instance for a newtype on top of (,,,), a "quad". (The standard library defines an instance for (,), but not (,,,). That's OK, as the standard implementation has different semantics than what I was after.)
The background is; I am parsing some old data, and the date format in the data is ambiguous. Each date in the data can be parsed into four possibilities, stored in the quad. I then want to validate each date in the quad to eliminate semantically invalid dates. (There are no months with 32 days, there is no month 34, there is no 5th quarter, etc.) Finally, I want to take each date in the dataset, and reduce the entire set to a quad representing which date formats are valid for the entire set. Then, I choose the best format out of those options, and assume that's what the date format of the dataset is.
This entire operation is very easy to express as applicative operations on the quad structure.
Here's the basic shape of the code:
My new type:
newtype DQ a = DQ (a, a, a, a) -- date quad
deriving ...
instance Functor DQ where
g `fmap` f = pure g <*> f
instance Applicative DQ where
pure x = DQ (x, x, x, x)
DQ (g, h, i, j) <*> DQ (a, b, c, d) = DQ (g a, h b, i c, j d)
Some prerequisite "pure" functions:
parseDateInt :: Int -> DQ Date
validateDate :: Date -> Bool
extractBestDate :: DQ Date -> DQ Bool -> Date
So once we have the quad of parsed dates (from parseDateInt), we need to validate them:
validateDates :: DQ Date -> DQ Bool
validateDates = (validateDate <$>)
(This is only a Functor so far, but you could also write
(pure validateDate <*>).
It's also worth noting the symmetry between validation of a single
element, and validating each element of the set -- to validate one, you might write
validateDate $ date; to validate a set, you write
validateDate <$> dates. This is why fmap is written as <$>, it's function application to a functor.)
The step after this is to take a set of valid parses and fold that into a final result:
intuitDateType :: [DQ Bool] -> DQ Bool
intuitDateType dates = foldl1 (liftA2 (&&)) dates
So now you can go from the [Int] in the data file to a DQ Bool
representing the possibly-valid date representations for a dataset.
(And from there, associate each data point with a real date object,
instead of the flaky Int that was supplied.)
So anyway, this post has gotten a bit long, but the idea is that an
Applicative instance allowed me to solve my problem in about 3 lines
of code. My problem domain was repeatedly applying functions to data in a
container, which is what an Applicative functor does. There is no join operation on this data, so a Monad instance would not make much sense.