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
TypeFamiliesfor this solution.The idea is to define a class
Predfor n-ary predicates:The problem is all about re-shuffling arguments to the predicates, so this is what the class aims to do. The associated type
Argis supposed to give access to the arguments of an n-ary predicate by replacing the finalBoolwithk, so if we have a typethen
This will allow us to build the right result type of
conjunctionwhere all arguments of the two predicates are to be collected.The function
splittakes a predicate of typeaand a continuation of typeBool -> rand will produce something of typeArg a r. The idea ofsplitis that if we know what to do with theBoolwe 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
Booland one for functions for which the target is already a predicate:A
Boolhas no arguments, soArg Bool ksimply returnsk. Also, forsplit, we have theBoolalready, so we can immediately apply the continuation.If we have a predicate of type
a -> r, thenArg (a -> r) kmust start witha ->, and we continue by callingArgrecursively onr. Forsplit, we can now take three arguments, thexbeing of typea. We can feedxtofand then callspliton the result.Once we have defined the
Predclass, it is easy to defineconjunction:The function takes two predicates and returns something of type
Arg a (Arg b Bool). Let’s look at the example:GHCi doesn’t expand this type, but we can. The type is equivalent to
which is exactly what we want. We can test a number of examples, too:
Note that using the
Predclass, it is trivial to write other functions (likedisjunction), too.