Today I played with using type classes to inductively construct functions of a predicate of any arity taking as inputs any combination of any types, that returned other predicates of the same type but with some basic operation applied. For example
conjunction (>2) even
would return a predicate that evaluates to true for even numbers larger than two and
conjunction (>=) (<=)
would return =
All well and good, got that part working, but it raised the question, what if I wanted to define conjunction of two predicates as being a predicate that takes one input for each input of each conjoined predicate? For example:
:t conjunction (>) not
would return Ord a => a -> a -> Bool -> Bool. Can this be done? If so, how?
We will need TypeFamilies
for this solution.
{-# LANGUAGE TypeFamilies #-}
The idea is to define a class Pred
for n-ary predicates:
class Pred a where
type Arg a k :: *
split :: a -> (Bool -> r) -> Arg a r
The problem is all about re-shuffling arguments to the predicates, so this is what the class aims to do. The associated type Arg
is supposed to give access to the arguments of an n-ary predicate by replacing the final Bool
with k
, so if we have a type
X = arg1 -> arg2 -> ... -> argn -> Bool
then
Arg X k = arg1 -> arg2 -> ... -> argn -> k
This will allow us to build the right result type of conjunction
where all arguments of the two predicates are to be collected.
The function split
takes a predicate of type a
and a continuation of type Bool -> r
and will produce something of type Arg a r
. The idea of split
is that if we know what to do with the Bool
we obtain from the predicate in the end, then we can do other things (r
) in between.
Not surprisingly, we'll need two instances, one for Bool
and one for functions for which the target is already a predicate:
instance Pred Bool where
type Arg Bool k = k
split b k = k b
A Bool
has no arguments, so Arg Bool k
simply returns k
. Also, for split
, we have the Bool
already, so we can immediately apply the continuation.
instance Pred r => Pred (a -> r) where
type Arg (a -> r) k = a -> Arg r k
split f k x = split (f x) k
If we have a predicate of type a -> r
, then Arg (a -> r) k
must start with a ->
, and we continue by calling Arg
recursively on r
. For split
, we can now take three arguments, the x
being of type a
. We can feed x
to f
and then call split
on the result.
Once we have defined the Pred
class, it is easy to define conjunction
:
conjunction :: (Pred a, Pred b) => a -> b -> Arg a (Arg b Bool)
conjunction x y = split x (\ xb -> split y (\ yb -> xb && yb))
The function takes two predicates and returns something of type Arg a (Arg b Bool)
. Let's look at the example:
> :t conjunction (>) not
conjunction (>) not
:: Ord a => Arg (a -> a -> Bool) (Arg (Bool -> Bool) Bool)
GHCi doesn't expand this type, but we can. The type is equivalent to
Ord a => a -> a -> Bool -> Bool
which is exactly what we want. We can test a number of examples, too:
> conjunction (>) not 4 2 False
True
> conjunction (>) not 4 2 True
False
> conjunction (>) not 2 2 False
False
Note that using the Pred
class, it is trivial to write other functions (like disjunction
), too.
{-# LANGUAGE TypeFamilies #-}
class RightConjunct b where
rconj :: Bool -> b -> b
instance RightConjunct Bool where
rconj = (&&)
instance RightConjunct b => RightConjunct (c -> b) where
rconj b f = \x -> b `rconj` f x
class LeftConjunct a where
type Ret a b
lconj :: RightConjunct b => a -> b -> Ret a b
instance LeftConjunct Bool where
type Ret Bool b = b
lconj = rconj
instance LeftConjunct a => LeftConjunct (c -> a) where
type Ret (c -> a) b = c -> Ret a b
lconj f y = \x -> f x `lconj` y
conjunction :: (LeftConjunct a, RightConjunct b) => a -> b -> Ret a b
conjunction = lconj
Hope it's self-explanatory, but if not, feel free to ask questions.
Also, you could merge the two classes into one, of course, but I feel that the two classes make the idea more clear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With