Necesitaremos TypeFamilies
para esta solución.
{-# LANGUAGE TypeFamilies #-}
La idea es definir una clase Pred
de predicados n-arias:
class Pred a where
type Arg a k :: *
split :: a -> (Bool -> r) -> Arg a r
El problema tiene que ver con los argumentos volver a barajar a los predicados, así que esto es lo que la clase tiene como objetivo hacer . El tipo asociado Arg
se supone para dar acceso a los argumentos de un predicado n-ary mediante la sustitución de la final Bool
con k
, así que si tenemos un tipo
X = arg1 -> arg2 -> ... -> argn -> Bool
entonces
Arg X k = arg1 -> arg2 -> ... -> argn -> k
Esto permitirá nosotros para construir el tipo de resultado correcto de conjunction
donde se deben recopilar todos los argumentos de los dos predicados.
La función split
toma un predicado de tipo a
y una continuación de tipo Bool -> r
y producirá algo de tipo Arg a r
. La idea de split
es que si sabemos qué hacer con el Bool
que obtenemos del predicado al final, entonces podemos hacer otras cosas (r
) en el medio.
No es de extrañar, necesitaremos dos casos, uno para Bool
y otro para las funciones para las que el destino ya es un predicado:
instance Pred Bool where
type Arg Bool k = k
split b k = k b
Un Bool
no tiene argumentos, por lo Arg Bool k
simplemente devuelve k
.Además, para split
, ya tenemos Bool
, por lo que podemos aplicar inmediatamente la continuación.
instance Pred r => Pred (a -> r) where
type Arg (a -> r) k = a -> Arg r k
split f k x = split (f x) k
Si tenemos un predicado de tipo a -> r
, entonces Arg (a -> r) k
debe comenzar con a ->
, y seguimos llamando Arg
recursivamente en r
. Para split
, ahora podemos tomar tres argumentos, el x
es del tipo a
. Podemos alimentar x
a f
y luego llamar al split
en el resultado.
Una vez que hemos definido la clase Pred
, es fácil definir conjunction
:
conjunction :: (Pred a, Pred b) => a -> b -> Arg a (Arg b Bool)
conjunction x y = split x (\ xb -> split y (\ yb -> xb && yb))
la función toma dos predicados y devuelve algo del tipo Arg a (Arg b Bool)
. Veamos el ejemplo:
> :t conjunction (>) not
conjunction (>) not
:: Ord a => Arg (a -> a -> Bool) (Arg (Bool -> Bool) Bool)
GHCi no expande este tipo, pero podemos. El tipo es equivalente a
Ord a => a -> a -> Bool -> Bool
que es exactamente lo que queremos. Podemos probar una serie de ejemplos, también:
> conjunction (>) not 4 2 False
True
> conjunction (>) not 4 2 True
False
> conjunction (>) not 2 2 False
False
Tenga en cuenta que el uso de la clase Pred
, es trivial para escribir otras funciones (como disjunction
), también.