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

Asnot没有在IsTalls 上定义,只有Bools。

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

基本上,我希望IsTalls 与其他IsTalls 交互,就像它们是Bools 一样,只是它们不会在没有显式强制转换的情况下与Bools 和s 交互。IsAlive

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


ps 我想我已经通过在 GHC 中的数字实现了这一点:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

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

(即 UserID 和 GroupID 不应该交互)

但我似乎无法用Bools 做到这一点(派生 Bool 不起作用)。我什至不确定以上是最好的方法。

4

3 回答 3

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

- 编辑

对于 && 之类的操作,您可以将其设为 Applicative 的实例

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阅读更多相关信息

于 2012-05-11T03:33:44.013 回答
9

您的选择是定义代数数据类型,例如

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

或定义新的运算符,例如 , ,&&&并在您选择的类型上重载它们。但即使重载,您也无法将它们与.|||complementif

无论如何,我不确定对高度的布尔运算是否有意义。你如何证明“高和矮等于矮”但“高或矮等于高”的结论?

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

PS Haskell 总是在获得新功能,所以我能说的最好的就是如果你可以超载if我不知道它。说 Haskell 说“不能做某事”总是很危险的……

于 2012-05-11T03:30:53.767 回答
8

newtype如果您导入 Prelude 隐藏您想要与您的IsTallIsAlive值一起使用的布尔函数,您可以使用 s 和一个类来实现这一点。您将布尔函数重新定义为类中的方法,然后为所有 3 BoolIsTallIsAlive类型创建实例。如果您使用GeneralizedNewtypeDeriving,您甚至可以获取IsTallandIsAlive实例,而无需手动编写包装/展开样板。

这是我在 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]

但是您将无法将 an 传递IsTall给在其他地方定义的任何其他需要 a 的函数Bool,因为其他模块仍将使用布尔函数的 Prelude 版本。像这样的语言结构if ... then ... else ...仍然是一个问题(尽管 hammar 对 Norman Ramsey 的回答的评论说你可以用另一个 GHC 扩展来解决这个问题)。我可能会toBool向该类添加一个方法,以帮助统一转换回常规Bools 以帮助缓解此类问题。

于 2012-05-11T05:13:28.327 回答