Don Stewart的Haskell in the Large演示文稿中提到了 * 幻影类型 *:
data Ratio n = Ratio Double
1.234 :: Ratio D3
data Ask ccy = Ask Double
Ask 1.5123 :: Ask GBP
字符串
我读了他关于这些问题的要点,但我不明白。此外,我读了关于这个问题的Haskell Wiki。但我仍然没有理解他们的观点。
使用幻影类型的动机是什么?
Don Stewart的Haskell in the Large演示文稿中提到了 * 幻影类型 *:
data Ratio n = Ratio Double
1.234 :: Ratio D3
data Ask ccy = Ask Double
Ask 1.5123 :: Ask GBP
字符串
我读了他关于这些问题的要点,但我不明白。此外,我读了关于这个问题的Haskell Wiki。但我仍然没有理解他们的观点。
使用幻影类型的动机是什么?
3条答案
按热度按时间yebdmbv41#
要回答“使用幻影类型的动机是什么”。有两点:
例如,您可以使用长度单位标记距离:
字符串
你可以避免火星气候轨道器灾难:
型
这个“模式”有一些细微的变化。你可以使用
DataKinds
来拥有封闭的单位集:型
它的工作原理类似于:
型
但是现在
Distance
只能以公里或英里为单位,我们不能在以后添加更多的单位。这在某些用例中可能有用。我们还可以:
型
在距离的情况下,我们可以计算出加法,例如,如果涉及到不同的单位,则转换为公里。但是这对于 * 货币 * 并不适用,因为它的比率随着时间的推移而变化。
可以使用GADT来实现,在某些情况下这可能是更简单的方法:
型
现在我们知道单位也在价值层面上:
型
这种方法特别简化了Aadit's answer的
Expr a
示例:型
值得指出的是,后一种变体需要非平凡的语言扩展(
GADTs
,DataKinds
,KindSignatures
),这可能在您的编译器中不受支持。bwleehnv2#
使用幻影类型背后的动机是专门化数据构造函数的返回类型。例如,考虑:
字符串
默认情况下,
Nil
和Cons
的返回类型都是List a
(这适用于所有a
类型的列表)。型
还要注意,
Nil
是一个幻影构造函数(也就是说,它的返回类型不依赖于它的参数,在这种情况下是空的,但仍然是一样的)。因为
Nil
是一个幻影构造函数,我们可以将Nil
专门化为任何我们想要的类型(例如Nil :: List Int
或Nil :: List Char
)。Haskell中的普通代数数据类型允许您选择数据构造函数的参数类型。例如,我们为上面的
Cons
选择了参数类型(a
和List a
)。然而,它不允许你选择数据构造函数的返回类型。返回类型总是泛化的。这对大多数情况都很好。但是,也有例外。例如:
型
数据构造函数的类型为:
型
正如你所看到的,所有数据构造函数的返回类型都是通用的。这是有问题的,因为我们知道
Number
和Increment
必须总是返回Expr Int
,Boolean
和Not
必须总是返回Expr Bool
。数据构造函数的返回类型是错误的,因为它们太通用了。例如,
Number
不可能返回Expr a
,但它确实返回了。这允许您编写类型检查器无法捕获的错误表达式。例如:型
问题是我们不能指定数据构造函数的返回类型。
请注意,
Expr
的所有数据构造函数都是幻影构造函数(即它们的返回类型不依赖于它们的参数)。构造函数都是幻影构造函数的数据类型称为幻影类型。请记住,像
Nil
这样的幻影构造函数的返回类型可以专门化为我们想要的任何类型。因此,我们可以为Expr
创建智能构造函数,如下所示:型
现在我们可以使用智能构造函数来代替普通构造函数,我们的问题就解决了:
型
因此,当你想专门化数据构造函数的返回类型时,幻影构造函数很有用,幻影类型是其构造函数都是幻影构造函数的数据类型。
注意,像
Left
和Right
这样的数据构造函数也是幻影构造函数:型
原因是,虽然这些数据构造函数的返回类型确实依赖于它们的参数,但它们仍然是通用的,因为它们只部分依赖于它们的参数。
了解数据构造函数是否是幻影构造函数的简单方法:
所有出现在数据构造函数返回类型中的类型变量是否也出现在数据构造函数的参数中?如果是,它不是幻影构造函数。
希望能帮上忙。
2wnc66cl3#
对于
Ratio D3
,我们使用丰富的类型来驱动类型导向的代码,例如,如果您有一个类型为Ratio D3
的字段,其编辑器将被分派到一个只接受数字输入并显示3位精度的文本字段。这与之相反,例如,对于newtype Amount = Amount Double
,我们不显示十进制数字,但是使用千个逗号并将输入如'10m'解析为'10,000,000'。在底层表示中,两者仍然只是
Double
s。