我有数据类型:
data Expr = Num Int
| Expression Expr Operator Expr
在问题的上下文中,(Num Int)
将代表的数字仅为单个数字。有没有办法确保类型声明中的限制?
当然我们可以定义一个函数来测试Expr
是否有效,但让类型系统处理它会很好。
答案 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
实例进行模运算,如果这样做会有帮助的话;这也可以让你使用数字文字来构建数字。 (同样适用于Enum
和Bounded
。)
但是,这并不能确保您永远不会尝试来创建无效的数字,只是您从未做。如果您需要更多保证,那么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