We had a good meeting last night. Seven people showed up. We had a "BYOC(&WFI)" (Bring Your Own Code (And We'll Fix It)) night. It turned into an interesting battle of wits to see who could generalize more.
We had some code that looked like this:
rule m = p1 m && ... && pk m || pk+1 m && ... && pn m
Then, we came up with a function that abstracted over the "obvious" pattern:
> dnf :: a -> [[a -> Bool]] -> Bool
> dnf x = or . map (and . map ($ x))
Then, we could rewrite rule as:
rule m = dnf m [[p1, ..., pk], [pk+1, ..., pn]]
But some of us weren't satisfied with that. So, in the end, we created our own library/DSL for predicates.
First, we need some imports:
> import Control.Applicative
> import Data.Foldable as F
Then, we define a predicate: any (applicative) functor with a Bool type argument:
> type Pred f = f Bool
Given that Bool is the argument, we can derive the basic predicate constructors from the Bool constructors:
> true :: Applicative f => Pred f
> true = pure True
> false :: Applicative f => Pred f
> false = pure False
Additionally, we lift Bool operations to work on predicates:
> (&&&) :: Applicative f => Pred f -> Pred f -> Pred f
> (&&&) = liftA2 (&&)
> (|||) :: Applicative f => Pred f -> Pred f -> Pred f
> (|||) = liftA2 (||)
We also need lifted 'and' and 'or' functions (conveniently (?) defined for anything Foldable):
> andP, orP :: (Applicative f, Foldable c) => c (Pred f) -> Pred f
> andP = F.foldl' (&&&) true
> orP = F.foldl' (|||) false
Finally, we have our new and improved function:
> dnf :: (Functor c, Applicative f, Foldable c, Foldable d) => c (d (Pred f)) -> Pred f
> dnf = orP . fmap andP
Unless you think you can do better? (Using Category and Arrow doesn't count: too trivial!)
Until next time!
Sean