2012-10-03 83 views
7

今天我玩弄了使用類型類來歸納地構造任何一個謂詞的函數作爲輸入的任何類型的任何組合,返回其他相同類型的謂詞但應用了一些基本操作。例如在Haskell中,我如何獲取一個m元謂詞和一個n元謂詞並構造一個(m + n)個謂詞?

conjunction (>2) even 

將返回值爲true甚至數超過兩個

大的謂詞
conjunction (>=) (<=) 

將返回=

一切都很好,得到了部分工作,但它提出了這個問題,如果我想將兩個謂詞的連接定義爲一個謂詞,每個連接謂詞的每個輸入都需要一個輸入,那該怎麼辦呢?例如:

:t conjunction (>) not 

會返回Ord a => a - > a - > Bool - > Bool。這可以做到嗎?如果是這樣,怎麼樣?

回答

6

對於此解決方案,我們需要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。我們可以提供xf,然後致電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),太。

4
{-# LANGUAGE TypeFamilies #-} 

class RightConjunct b where 
    rconj :: Bool -> b -> b 

instance RightConjunct Bool where 
    rconj = (&&) 

instance RightConjunct b => RightConjunct (c -> b) where 
    rconj b f = \x -> b `rconj` f x 

class LeftConjunct a where 
    type Ret a b 
    lconj :: RightConjunct b => a -> b -> Ret a b 

instance LeftConjunct Bool where 
    type Ret Bool b = b 
    lconj = rconj 

instance LeftConjunct a => LeftConjunct (c -> a) where 
    type Ret (c -> a) b = c -> Ret a b 
    lconj f y = \x -> f x `lconj` y 

conjunction :: (LeftConjunct a, RightConjunct b) => a -> b -> Ret a b 
conjunction = lconj 

希望它是不言自明的,但如果沒有,請隨時提問。

另外,您可以將兩個類合併成一個類,當然,但我覺得這兩個類讓這個想法更清晰。