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 a
和 Right 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
类型定义自己的值。
我省略了 Num
、Integral
、Eq
、Ord
等实例的定义,因为我认为他们不会添加更多我的解释。
Haskell 在编译代码时检查 types,而不是值。让类型依赖于值是 "dependent types" 的工作。这是一个高级话题。
实现此目的的另一种方法是让您的 hanoiDisk
工作 而不是 与 Integer
s,但与一些“PositiveInteger
”不可能为负的类型(或者也为 0..?)。这是一种更基本的方法。
没有什么可断言的——你应该不可能用这种类型写下一个负值。您必须使此类型成为 Num
、Eq
、Ord
和 Show
(也可能是 Enum
)的实例。
通常的方式是定义
data Nat = Z | S Nat
deriving (Eq, Show)
我正在通过讲座学习 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 a
和 Right 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
类型定义自己的值。
我省略了 Num
、Integral
、Eq
、Ord
等实例的定义,因为我认为他们不会添加更多我的解释。
Haskell 在编译代码时检查 types,而不是值。让类型依赖于值是 "dependent types" 的工作。这是一个高级话题。
实现此目的的另一种方法是让您的 hanoiDisk
工作 而不是 与 Integer
s,但与一些“PositiveInteger
”不可能为负的类型(或者也为 0..?)。这是一种更基本的方法。
没有什么可断言的——你应该不可能用这种类型写下一个负值。您必须使此类型成为 Num
、Eq
、Ord
和 Show
(也可能是 Enum
)的实例。
通常的方式是定义
data Nat = Z | S Nat
deriving (Eq, Show)