haskell - “type”与“newtype”中类型参数的奇怪行为:这是一个错误还是一个特性?
问题描述
我正在编写一个类型类来向 Haskell 数据类型添加类型反射。它的一部分看起来像这样:
type VarName a = Text
class Reflective a where
-- | A reflective type may have a fixed name, or it may be a union
-- where each variant has a distinct name.
reflectiveName :: a -> VarName a
-- | One default value of the type for each reflective name.
reflectiveDefaults :: [a]
这个想法是,如果我写
data ExampleType = Foo Int | Bar String
然后在Reflective
实例reflectiveName
中将酌情返回“Foo”或“Bar”,reflectiveDefaults
并将返回[Foo 0, Bar ""]
所以现在我可以编写一个函数来给我所有变体的名称:
reflectiveNames :: (Reflective a) => [VarName a]
reflectiveNames = map reflectiveName reflectiveDefaults
我可以这样称呼它:
exampleNames = reflectiveNames :: [VarName ExampleType]
当我编译这个时,我在类型声明中得到以下错误reflectiveNames
:
• Could not deduce (Reflective a0)
from the context: Reflective a
bound by the type signature for:
reflectiveNames :: Reflective a => [VarName a]
The type variable ‘a0’ is ambiguous
但是,如果我用新类型替换 VarName:
newtype VarName a = VarName Text
然后它工作。
这是 Haskell 类型系统的一个特性,还是 GHC 中的一个错误?如果是前者,为什么要发明一个新的类型变量a0?
解决方案
为什么这失败了type
...
如果您编写type
,那么您不会构造新类型,而是构造别名。所以你定义了:
type VarName a = Text
所以现在你每次写VarName SomeType
,基本都写完了Text
。所以VarName Char ~ VarName Int ~ Text
(波浪号~
表示两种类型相等)。
然而,类型别名很有用,因为它们通常会最小化代码(通常别名的名称比对应的名称短),它降低了签名的复杂性(不必记住大量的类型层次结构),最后它可以如果某些类型尚未完全确定(例如,时间可以建模为Int32
,Int64
等)并且我们想要定义一些占位符以轻松更改大量签名,则使用它。
但关键是每次你写一个VarName Char
例子,Haskell 都会用Text
. 所以现在如果我们看看你的函数,你已经写了:
reflectiveNames :: Reflective a => [Text]
reflectiveNames = map reflectiveName reflectiveDefaults
现在这个函数有一个问题:有一个类型变量a
(in Reflective a
),但在签名中没有任何地方使用这个类型参数。问题是 Haskell 不知道a
在调用此函数时要填写什么,这是一个真正的问题(此处),因为 and 的语义对于 anreflectiveName
和anreflectiveDefaults
可能完全不同。编译器不能只为选择一个类型,因为这意味着两个不同的 Haskell 编译器最终可能会产生产生不同输出的函数,从而产生不同的程序(通常编程语言的基本期望方面之一是明确性a ~ Char
a ~ Int
a
,事实上没有两个语义不同的程序映射到相同的源代码上)。
...以及为什么它适用于newtype
现在,如果我们使用,为什么不会发生这种情况newtype
?基本上 a与声明newtype
相同data
,除了一些小细节:例如在幕后,Haskell不会生成这样的构造函数,它只会存储包装在构造函数中的值,但它会将值视为不同的类型. newtype
定义_
newtype VarName a = VarName Text
因此(在概念上)几乎等同于:
data VarName a = VarName Text
尽管 Haskell 会(假设它是一个可以处理这种优化的编译器)将构造函数分解掉,但我们可以从概念上假设它存在。
但主要区别在于我们没有定义类型签名:我们定义了一个新类型,所以函数签名保持不变:
reflectiveNames :: Reflective a => [VarName a]
reflectiveNames = map reflectiveName reflectiveDefaults
而且我们不能只写而Text
不是VarName a
,因为 aText
不是a 。这也意味着 Haskell 可以完美地推导出是什么。例如,如果我们要触发,那么它知道那是 a ,因此它将使用of for 。没有歧义。当然,我们可以定义别名,例如:VarName a
a
reflectiveNames :: [VarName Char]
a
Char
instance
Reflective
a ~ Char
type Foo = VarName Char -- a ~ Char
type Bar b = VarName Int -- a ~ Int
但后来还是分别a
解决了。由于这是一个新的类型,我们总是会在代码中携带的类型,因此代码是明确的。Char
Int
a
推荐阅读
- c - 循环中的 C LoadLibrary
- java - 我需要微服务中的事务队列吗?
- python - 从文件夹导入 JPG 文件以进行 PyTorch 转换(另一个错误)?
- javascript - 当值来自父组件时,如何在文本区域中使用拼音键盘?
- php - Nginx 与 try_files 指令一起工作很奇怪
- c++ - 收回指针值的问题
- android - Kotlin SimpleDateFormat 到字符串
- javascript - 为什么浏览器在主机为0.0.0.0的情况下会尝试通过https获取文件?
- database - SQLite 3 无法读取数据库的编辑
- excel - VLOOKUP 单元格中的每个单词以执行缩写解码