このソリューションにはTypeFamilies
が必要です。
{-# LANGUAGE TypeFamilies #-}
アイデアはn項述語のクラスPred
を定義することです:
class Pred a where
type Arg a k :: *
split :: a -> (Bool -> r) -> Arg a r
問題は、すべての述語への再シャッフル引数についてですので、これはクラスが行うことを目的とするものです。関連するタイプArg
はk
で最終Bool
を置き換えることにより、n項述語の引数へのアクセス権を与えることになっているので、我々はタイプそして
X = arg1 -> arg2 -> ... -> argn -> Bool
Arg X k = arg1 -> arg2 -> ... -> argn -> k
を持っている場合、これができるようになります我々はconjunction
の正しい結果型を構築し、2つの述語のすべての引数を収集することにしました。
機能split
はタイプa
の述語と種類Bool -> r
の継続を取り、タイプArg a r
の何かを生成します。 split
というアイデアは、最後に述語から取得するBool
をどうすればよいか分かっていれば、その間に他のもの(r
)を実行できます。
驚くほど、我々は2つのインスタンス、Bool
用とターゲットがすでに述語される機能のための1つをする必要がありますない:
instance Pred Bool where
type Arg Bool k = k
split b k = k b
Bool
は引数を持っていないので、Arg Bool k
は単にk
を返します。また、split
については、Bool
が既にありますので、すぐに継続を適用できます。私たちはタイプa -> r
の述語を持っている場合
instance Pred r => Pred (a -> r) where
type Arg (a -> r) k = a -> Arg r k
split f k x = split (f x) k
、そしてArg (a -> r) k
はa ->
で始まる必要があり、我々はr
に再帰的にArg
を呼び出すことで継続します。 split
については、x
がa
の3つの引数を取ることができるようになりました。 x
からf
にフィードし、結果としてsplit
を呼び出すことができます。
我々はconjunction
を定義することは容易である、Pred
クラスを定義したら:
conjunction :: (Pred a, Pred b) => a -> b -> Arg a (Arg b Bool)
conjunction x y = split x (\ xb -> split y (\ yb -> xb && yb))
関数は二つの述語を取り、タイプArg a (Arg b Bool)
の何かを返します。例を見てみましょう:
> :t conjunction (>) not
conjunction (>) not
:: Ord a => Arg (a -> a -> Bool) (Arg (Bool -> Bool) Bool)
GHCiはこのタイプを展開しません。タイプは
Ord a => a -> a -> Bool -> Bool
と同等です。これはまさに私たちが望むものです。我々はあまりにも、多くの例をテストすることができます。
> conjunction (>) not 4 2 False
True
> conjunction (>) not 4 2 True
False
> conjunction (>) not 2 2 False
False
注Pred
クラスを使用して、あまりにも、(disjunction
のような)他の関数を書くことが些細であること。