Będziemy potrzebować TypeFamilies
dla tego rozwiązania.
{-# LANGUAGE TypeFamilies #-}
Chodzi o to, aby zdefiniować klasę Pred
dla n-arnych predykatów:
class Pred a where
type Arg a k :: *
split :: a -> (Bool -> r) -> Arg a r
Problem jest o argumentach ponownie tasowania do orzeczników, więc to, co ma klasa zrobić . Powiązany typ Arg
ma dać dostęp do argumentów n-ary orzecznika zastępując końcowy Bool
z k
, więc jeśli mamy typ
X = arg1 -> arg2 -> ... -> argn -> Bool
następnie
Arg X k = arg1 -> arg2 -> ... -> argn -> k
To pozwoli nam zbudować odpowiedni typ wyniku: conjunction
, gdzie wszystkie argumenty z dwóch predykatów mają być zebrane.
Funkcja split
pobiera predykat typu a
i kontynuację typu Bool -> r
i wygeneruje coś typu Arg a r
. Pomysł na split
polega na tym, że jeśli wiemy, co zrobić z Bool
, które uzyskujemy z predykatu na końcu, możemy zrobić między innymi inne rzeczy (r
).
Nic dziwnego, że musimy dwa przypadki, jeden dla Bool
i jeden dla funkcji, dla których cel jest już orzeczenie:
instance Pred Bool where
type Arg Bool k = k
split b k = k b
Bool
ma żadnych argumentów, więc Arg Bool k
prostu zwraca k
.Również dla split
mamy już Bool
, więc możemy od razu zastosować kontynuację.
instance Pred r => Pred (a -> r) where
type Arg (a -> r) k = a -> Arg r k
split f k x = split (f x) k
Jeśli mamy predykat typu a -> r
, następnie Arg (a -> r) k
musi zaczynać się a ->
, a my nadal wywołując Arg
rekurencyjnie na r
. W przypadku split
możemy teraz przyjąć trzy argumenty: x
będący typu a
. Możemy podać x
do f
, a następnie wywołać split
na wyniku.
Po zdefiniowaniu klasy Pred
, to jest łatwe do zdefiniowania conjunction
:
conjunction :: (Pred a, Pred b) => a -> b -> Arg a (Arg b Bool)
conjunction x y = split x (\ xb -> split y (\ yb -> xb && yb))
Funkcja przyjmuje dwa predykaty i zwraca coś typu Arg a (Arg b Bool)
. Spójrzmy na przykład:
GHCi nie rozszerza tego typu, ale możemy. Typ jest równoważny z
Ord a => a -> a -> Bool -> Bool
który jest dokładnie tym, czego chcemy. Możemy przetestować szereg przykładów, za:
> conjunction (>) not 4 2 False
True
> conjunction (>) not 4 2 True
False
> conjunction (>) not 2 2 False
False
Należy pamiętać, że przy użyciu klasy Pred
, to jest trywialne napisać inne funkcje (np disjunction
), też.