Haskell:使用逻辑上不同的布尔值键入安全性

时间:2012-05-11 03:19:30

标签: haskell types casting type-safety

假设我有以下代码

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,因此只有Bool s。

我可以一直明确地提取Bool,但这很大程度上违背了目的。

基本上,我希望IsTall与其他IsTall进行互动,就像他们Bool一样,除非他们不会与Bool进行互动IsAlive没有明确的演员。

实现这一目标的最佳方式是什么。


P.S。我想我已经通过在GHC中做到了这个数字:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

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

(即UserID和GroupID不应该互动)

但我似乎无法使用Bool s(导出Bool不起作用)。我甚至不确定以上是最好的方法。

3 个答案:

答案 0 :(得分: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

- 编辑

用于&amp;&amp;等操作你可以把它变成一个Applicative的实例

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

然后你可以使用liftA2

进行(&amp;&amp;)

示例:

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

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

了解更多相关信息

答案 1 :(得分:9)

您可以选择定义代数数据类型,如

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

或定义新的运算符,例如&&&|||complement,并根据您选择的类型重载它们。但即使有重载,您也无法将其与if一起使用。

我不确定高度上的布尔运算是否有意义。你如何证明“高而短等于短”但“高或短等于高”的结论?

我建议你为你的连接词寻找不同的名字,然后你可以超载。

P.S。 Haskell总是得到新的功能,所以我能说的最好的是,如果你可以重载if我不知道它。要说Haskell“不能做到这样的事情”总是危险的......

答案 2 :(得分:8)

如果您导入Prelude隐藏要与newtypeIsTall值一起使用的布尔函数,则可以使用IsAlive和类来获得某种方法。您将布尔函数重新定义为类中的方法,然后为其中的所有3个BoolIsTallIsAlive类型创建实例。如果您使用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 ...这样的语言结构也仍然是一个问题(虽然hammar对Norman Ramsey的回答说你可以用另一个GHC扩展来解决这个问题)。我可能会在该类中添加toBool方法,以帮助统一转换回常规Bool以帮助缓解此类问题。