gpt4 book ai didi

haskell - "type"与 "newtype": is this a bug or a feature? 中类型参数的奇怪行为

转载 作者:行者123 更新时间:2023-12-02 12:14:09 25 4
gpt4 key购买 nike

我正在编写一个类型类来向 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,那么您不会构造新类型,而是构造一个别名。所以你定义了:

<b>type</b> VarName a = Text

因此,现在每次您编写 VarName SomeType 时,您基本上都编写了 Text。因此 VarName Char ~ VarName Int ~ Text (波形符 ~ 表示这两种类型相等)。

类型别名很有用,但是因为它们通常会最小化代码(通常别名的名称比其对应名称短),它降低了签名的复杂性(不必记住一大堆类型),最后如果某些类型尚未完全确定(例如时间可以建模为 Int32Int64 等)并且我们想要定义一些占位符来使用它,轻松更改大量签名。

但重点是,例如,每次编写 VarName Char 时,Haskell 都会将其替换为 Text。现在,如果我们看一下您的函数,您已经编写了:

reflectiveNames :: Reflective <b>a</b> => [<b>Text</b>]
reflectiveNames = map reflectiveName reflectiveDefaults

现在这个函数有一个问题:有一个类型变量a(在Reflective a中),但是我们在签名中没有任何地方使用这个类型参数。问题是 Haskell 不知道在调用此函数时要填写什么 a,这是一个真正的问题(此处),因为 reflectiveName 和对于 a ~ Chara ~ Int 来说,reflectedDefaults 可能完全不同。编译器不能只是“挑选a的类型,因为这意味着两个不同的Haskell编译器最终可能会得到生成不同输出的函数,因此不同的程序(通常编程语言的基本期望方面之一是明确性,即不存在映射到相同源代码的两个语义不同的程序)。

...以及为什么它适用于newtype

现在,如果我们使用newtype,为什么不会发生这种情况?基本上,newtypedata 声明相同,除了一些小细节:例如,在幕后,Haskell 将不会生成这样的构造函数,它只会存储包装在构造函数内的值但是它将将该值视为不同的类型。 newtype 定义

<b>newtype</b> VarName a = VarName Text

因此(概念上)几乎等同于:

<b>data</b> VarName a = VarName Text

虽然 Haskell 会(假设它是一个可以处理此类优化的编译器)分解构造函数,但我们可以从概念上假设它就在那里。

但主要区别在于我们没有定义类型签名:我们定义了一个新类型,因此函数签名保持不变:

reflectiveNames :: Reflective a => [<b>VarName a</b>]
reflectiveNames = map reflectiveName reflectiveDefaults

我们不能只写 Text 而不是 VarName a,因为 Text 不是 变量名称。这也意味着 Haskell 可以完美地推导 a 是什么。例如,如果我们触发 reflectiveNames::[VarName Char],那么它就知道 aChar,因此它将使用a ~ CharReflective实例。没有任何歧义。当然我们可以定义别名,例如:

type Foo = VarName <b>Char</b>   -- a ~ Char
type Bar <b>b</b> = VarName <b>Int</b> -- a ~ Int

但是a仍然分别解析为CharInt。由于这是一个新类型,我们将始终在代码中携带 a 的类型,因此代码是明确的。

关于haskell - "type"与 "newtype": is this a bug or a feature? 中类型参数的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50640657/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com