限制类型构造函数中的值

时间:2011-12-14 19:36:26

标签: haskell

  

可能重复:
  How to create a type bounded within a certain range

我有数据类型:

data Expr = Num Int
          | Expression Expr Operator Expr

在问题的上下文中,(Num Int)将代表的数字仅为单个数字。有没有办法确保类型声明中的限制?

当然我们可以定义一个函数来测试Expr是否有效,但让类型系统处理它会很好。

2 个答案:

答案 0 :(得分:12)

您可以将抽象数据类型与 smart constructor

一起使用
newtype Digit = Digit { digitVal :: Int }
  deriving (Eq, Ord, Show)

mkDigit :: Int -> Maybe Digit
mkDigit n
  | n >= 0 && n < 10 = Just (Digit n)
  | otherwise = Nothing

如果你把它放在另一个模块中并且不导出Digit构造函数,那么客户端代码不能构造范围[0,9]之外的Digit类型的值,但是你必须手动包装和展开才能使用它。您可以定义一个Num实例进行模运算,如果这样做会有帮助的话;这也可以让你使用数字文字来构建数字。 (同样适用于EnumBounded。)

但是,这并不能确保您永远不会尝试来创建无效的数字,只是您从未。如果您需要更多保证,那么Jan提供的手动解决方案会更好,但代价是不方便。 (如果你为该Digit类型定义一个Num实例,它最终会“不安全”,因为你可以写42 :: Digit,这要归功于你得到的数字文字支持。)

(如果您不知道newtype是什么,对于具有单个严格字段的​​数据类型,它基本上是data; T 周围的newtype包装器将具有与 T 相同的运行时表示。它基本上只是一个优化,所以你可以假装它data来理解这个。)

编辑:对于更多以理论为导向的100%解决方案,请参阅此答案的相当狭窄的评论部分。

答案 1 :(得分:5)

由于只有十种可能性,您可以使用Enum来指定所有这些可能性。

data Digit = Zero | One | Two deriving (Enum, Show)

然后你必须使用fromEnum将它们视为数字。

1 == fromEnum One

同样,使用toEnum,您可以从数字中获得Digit

toEnum 2 :: Digit

我们可以更进一步实施Num

data Digit = Zero | One | Two deriving (Enum, Show, Eq)

instance Num Digit where
  fromInteger x = toEnum (fromInteger x) :: Digit
  x + y = toEnum $ fromEnum x + fromEnum y
  x * y = toEnum $ fromEnum x * fromEnum y
  abs = id
  signum _ = 1

Zero + 1 + One == Two