2012-05-11 53 views
7

類型安全可以說,我已經得到了下面的代碼哈斯克爾:與邏輯上不同的布爾值

type IsTall = Bool 
type IsAlive = Bool 

is_short_alive_person is_tall is_alive = (not is_tall) && is_alive 

說,後來,我有以下

a :: IsAlive 
a = False 

b :: IsTall 
b = True 

,並調用以下,圍繞錯誤的方式得到兩個參數:

is_short_alive_person a b 

這成功地編譯不幸,並在運行時高死亡人員而不是短暫的活着的人。

我想上面的例子不編譯。

我第一次嘗試是:

newtype IsAlive = IsAlive Bool 
newtype IsTall = IsTall Bool 

但我不能這樣做。

switch_height :: IsTall -> IsTall 
switch_height h = not h 

作爲not上沒有IsTall S,只Bool已定義。

我可以一直明確地提取Bool,但這在很大程度上破壞了目的。

基本上,我想IsTall s到與其他IsTall小號互動,就像他們Bool S,只是它們不會Bool S和IsAlive•不用顯式轉換交互。

實現這一目標的最佳方法是什麼?


p.s.我想我已經做在GHC與數字實現這一點:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

newtype UserID = UserID Int deriving (Eq, Ord, Num) 
newtype GroupID = GroupID Int deriving (Eq, Ord, Num) 

(即用戶名的和羣ID的不應該交互)

,但我似乎不能與Bool S(派生BOOL要做到這一點不起作用)。無論如何,我甚至不確定以上是最好的方法。

+1

你想要的'IsTall'和'IsAlive'類型是一個可怕的想法。這是使用不相交類型來確保類型安全的一般體面思想的錯誤概括。將它與你的'UserID'和'GroupID'進行比較;在這種情況下,具有獨立的類型是有意義的,因爲將'UserId'傳遞到需要'GroupID'的地方是沒有意義的,或者將一個添加到另一個(儘管可能都不應該實現「Num」)。然而,測試一個人是否高大*和*活着,高*還是*活着,*不高*和*活着等等是有意義的。 –

+0

sacundim:在很大程度上,我改變了我的想法到這一點視圖。我最終使用了'newtype Height = Tall | Short「然後做'x == Tall'等。稍微輸入一點,但我認爲它使得代碼更具可讀性和類型安全性。 – Clinton

回答

8

閱讀更多關於這個你可以朝着這個某種方式,使用newtype S和類,如果您導入隱藏您想要使用的布爾函數與您的IsTallIsAlive值。您將布爾函數重新定義爲該類中的方法,然後爲此爲類型的所有3個實例創建實例,包括Bool,IsTallIsAlive類型中的全部三個。如果您使用的是GeneralizedNewtypeDeriving,您甚至可以獲得IsTallIsAlive實例,而無需手動編寫包裝/展開樣板文件。

下面是一個示例腳本,我居然在ghci中嘗試了:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

import Prelude hiding ((&&), (||), not) 
import qualified Prelude 

class Boolish a where 
    (&&) :: a -> a -> a 
    (||) :: a -> a -> a 
    not :: a -> a 

instance Boolish Bool where 
    (&&) = (Prelude.&&) 
    (||) = (Prelude.||) 
    not = Prelude.not 

newtype IsTall = IsTall Bool 
    deriving (Eq, Ord, Show, Boolish) 

newtype IsAlive = IsAlive Bool 
    deriving (Eq, Ord, Show, Boolish) 

現在,您可以任意三種&&||,並且not價值,卻不能在一起。它們是單獨的類型,所以你的函數簽名現在可以限制他們想要接受的3箇中的哪一個。在其他模塊中定義

高階函數將正常工作與此,如:

*Main> map not [IsTall True, IsTall False] 
[IsTall False,IsTall True] 

但你不能給IsTall傳遞到其他地方定義的其他功能期望一個Bool,因爲另一個模塊仍將使用布爾函數的Prelude版本。像if ... then ... else ...這樣的語言結構仍然是一個問題(雖然hamman對Norman Ramsey的回答發表評論說你可以用另一個GHC擴展來解決這個問題)。我可能會添加一個toBool方法到該類,以幫助統一轉換回常規Bool s,以幫助緩解此類問題。

+0

更新了答案,如果你想採用這種方法,http://hackage.haskell.org/package/cond是很有用的。 –

8

你的選擇是要麼定義代數數據類型,如

data Height = Tall | Short 
data Wiggliness = Alive | Dead 

,或者定義新的運營商,例如,&&&|||complement和超載他們在您選擇的類型。但即使超載,您也不能將其與if一起使用。

無論如何,我不確定高度的布爾操作是否有意義。你如何證明「高與低等於短」而「高與短等於高」的結論?

我建議你爲你的連接詞尋找不同的名字,然後你可以重載。

P.S. Haskell總是獲得新功能,所以最好的我可以說的是,如果你可以超載if我不知道它。要說Haskell「不能做這樣的事情」總是很危險......

+3

您可以在最近版本的GHC中使用'RebindableSyntax'擴展名重載'if'。 [快速示例](https://gist.github.com/2657492)。 – hammar

+0

@Norman:我嘗試了Ben的答案,這個答案相當不錯,但最終我決定按照您的建議定義Enums更簡潔。我意識到我可以在沒有邏輯操作的情況下做我所做的事情。 – Clinton

11

如果你稍微改變你的數據類型,你可以使它成爲一個Functor的實例,然後你可以使用fmap來做在布爾

import Control.Applicative 

newtype IsAliveBase a = IsAlive a 
newtype IsTallBase a = IsTall a 

type IsAlive = IsAliveBase Bool 
type IsTall = IsTallBase Bool 

instance Functor IsAliveBase where 
    fmap f (IsAlive b) = IsAlive (f b) 

instance Functor IsTallBase where 
    fmap f (IsTall b) = IsTall (f b) 

switch_height :: IsTall -> IsTall 
switch_height h = not <$> h -- or fmap not h 

操作 - 編輯

的操作,例如& &你可以把它應用型

的實例
instance Applicative IsAliveBase where 
    pure = IsAlive 
    (IsAlive f) <*> (IsAlive x) = IsAlive (f x) 

,然後你可以做(​​& &)使用liftA2

例如:

*Main> let h = IsAlive True 
*Main> liftA2 (&&) h h 
IsAlive True 

你可以在http://en.wikibooks.org/wiki/Haskell/Applicative_Functors

+0

我該如何做h1 && h2? – Clinton

+0

用&& – Phyx