Haskell 智能构造函数的编译时检查

Haskell compile time checking of smart constructors

我正在通过讲座学习 Haskell、运行: http://www.cis.upenn.edu/~cis194/spring13/

我有:

module HanoiDisk(HanoiDisk, hanoiDisk) where
import Control.Exception
data HanoiDisk = HanoiDisk' Integer deriving (Show)
hanoiDisk :: Integer -> HanoiDisk 
hanoiDisk n = assert (n >= 1) $ HanoiDisk' n

这有效,但如果我有:

main = do 
  print(show (hanoiDisk (-3))

我只在 运行 期间出错,而不是在编译时出错。

我非常想知道如何完全消除 运行 时间异常。

谁能提供替代方法?

谢谢

据我了解,当有人将函数 hanoiDisk 应用于小于 1 的参数时,您需要一种方法 "fail nicely"。

正如评论者所说,在编译时执行此操作超出了基本 Haskell 的范围,您在日常代码中不需要它!

你绝对可以 "fail nicely" 使用 Either a b 数据类型。

这个想法是,如果你有一个函数 hanoiDisk :: Integer -> HanoiDisk 接受一个 Integer 并且如果输入是 [=63] 并且应该 return 一个 HanoiDisk 值=] 和输入为 "bad" 时的某种错误值,您可以使用备用构造函数对其进行编码。

Either a b 数据类型的构造函数是 Left aRight b,其中 错误输出的格式为 Left a,正常输出的格式为 Right b。让我们用这个重写你的函数。

hanoiDisk :: Integer -> Either String HanoiDisk 
hanoiDisk n = if n >= 1 
              then Right (HanoiDisk' n)
              else Left "a hanoi disk must be least 1"

(可能)更合适的答案

让我们讨论以编译器可接受的方式构造必须为非负数(相对于正数)的更简单的问题。

我认为问题与编译器解析数字的方式有关。任何时候您在程序中使用符号“0”、“1”、“2”、“3”、“4”、...、“9”来表示数字时,语言解析器都希望最终结果符合类型如 Int、Double 等,因此当您使用这些符号时,您会发现 someone 可能会在数字序列前添加一个“-”并将您的非负数变成负数。

让我们创建一个名为 Natural 的新模块,它将允许我们创建正数。在其中,我们使用每个符号名称的前两个字母为符号“0”、...、“1”定义 "aliases"(例如,tw 表示“2”)。由于人类使用十进制系统书写自然数,因此我们创建了一个名为 Natural 的数据类型,它接受两个参数——我们所表示的数字的第一个数字,然后是后续数字的列表。最后,我们选择性地从模块中导出函数,以禁止用户"misuse"

module Natural (ze,on,tw,th,fo,fi,si,se,ei,ni,Natural(..)) where

newtype Digit = Digit Int

ze = Digit 0
on = Digit 1
tw = Digit 2
th = Digit 3
fo = Digit 4
fi = Digit 5
si = Digit 6
se = Digit 7
ei = Digit 8
ni = Digit 9

data Natural = Nat Digit [Digit]

例如,自然数 312 将表示为 Nat th [on,tw]

任何导入 Natural 的模块只能访问我们导出的函数,因此尝试使用其他任何东西来定义类型 Natural 的值将导致编译错误。此外,由于我们没有导出 Digit 构造函数,因此导入器无法为 Digit 类型定义自己的值。

我省略了 NumIntegralEqOrd 等实例的定义,因为我认为他们不会添加更多我的解释。

Haskell 在编译代码时检查 types,而不是值。让类型依赖于值是 "dependent types" 的工作。这是一个高级话题。

实现此目的的另一种方法是让您的 hanoiDisk 工作 而不是 Integers,但与一些“PositiveInteger”不可能为负的类型(或者也为 0..?)。这是一种更基本的方法。

没有什么可断言的——你应该不可能用这种类型写下一个负值。您必须使此类型成为 NumEqOrdShow(也可能是 Enum)的实例。

通常的方式是定义

data Nat = Z | S Nat 
           deriving (Eq, Show)