對於此解決方案,我們需要TypeFamilies
。
{-# LANGUAGE TypeFamilies #-}
的想法是定義一個類Pred
爲n元謂詞:
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
的正確結果類型,其中收集兩個謂詞的所有參數。
函數split
取決於類型a
的謂詞和類型Bool -> r
的延續,並將生成Arg a r
類型的內容。 split
的想法是,如果我們知道我們最終從謂詞中獲得Bool
怎麼辦,那麼我們可以在其間做其他事情(r
)。
不足爲奇的是,我們需要兩個實例,一個用於Bool
和一個功能,其目標已經是一個謂語:
instance Pred Bool where
type Arg Bool k = k
split b k = k b
一個Bool
沒有參數,所以Arg Bool k
簡單地返回k
。另外,對於split
,我們已經有Bool
,所以我們可以立即應用延續。
instance Pred r => Pred (a -> r) where
type Arg (a -> r) k = a -> Arg r k
split f k x = split (f x) k
如果我們有a -> r
類型的謂詞,然後Arg (a -> r) k
必須a ->
開始,我們將繼續通過調用Arg
遞歸上r
。對於split
,我們現在可以採用三個參數爲a
。我們可以提供x
至f
,然後致電split
。
一旦我們已經定義了Pred
類,它很容易定義conjunction
:
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
),太。