将约束文字分配给多态变量

3 haskell constraints

在学习 Haskell 时,从第一原理开始使用 Haskell 编程发现了一个令我困惑的练习。

\n

这是简短的版本:

\n
For the following definition:\n   a) i :: Num a => a\n      i = 1\n   b) Try replacing the type signature with the following:\n      i :: a\n
Run Code Online (Sandbox Code Playgroud)\n

替换给我一个错误:

\n
error:\n    \xe2\x80\xa2 No instance for (Num a) arising from the literal \xe2\x80\x981\xe2\x80\x99\n      Possible fix:\n        add (Num a) to the context of\n          the type signature for:\n            i\' :: forall a. a\n    \xe2\x80\xa2 In the expression: 1\n      In an equation for \xe2\x80\x98i\'\xe2\x80\x99: i\' = 1\n   |\n38 | i\' = 1\n   |      ^\n\n
Run Code Online (Sandbox Code Playgroud)\n

我或多或少清楚 Num 约束是如何产生的。\n不清楚为什么分配1给多态变量i\'会产生错误。

\n

为什么这有效:

\n
id 1\n
Run Code Online (Sandbox Code Playgroud)\n

而这个则没有:

\n
i\' :: a\ni\' = 1\nid i\'\n
Run Code Online (Sandbox Code Playgroud)\n

如果没有问题,是否可以将更具体的值分配给不太具体的值并丢失一些类型信息?

\n

lef*_*out 6

这是一个常见的误解。您可能会想到,在 OO 类语言中,

class Object {};

class Num: Object { public: Num add(...){...} };

class Int: Num { int i; ... };
Run Code Online (Sandbox Code Playgroud)

然后您就可以使用一个Int值作为需要参数的函数的参数Num,或者Num使用一个值作为需要Object.

但这根本不是 Haskell 类型类的工作方式。Num不是一类值(例如,在上面的示例中,它是属于其中一个子类的所有值的类)。相反,它是代表特定类型数字的所有类型的类。

那有什么不同呢?嗯,像这样的多态文字1 :: Num a => a不会生成特定的Num值,然后可以将其向上转换为更通用的类。相反,它希望调用者首先选择要在其中呈现数字的具体类型,然后立即以该类型生成数字,然后类型永远不会改变。

换句话说,多态值具有隐式类型级参数。谁想用谁就用i需要在双方都同意的情况下这样做

  1. 明确什么类型a应该使用(它不一定需要在那里修复:调用者本身也可以是一个多态函数。)
  2. 编译器可以证明这个类型a有一个Num实例。

在 C++ 中,Haskell 类型类/多态文字的类似物不是[子]类及其对象,而是模板受限于某个概念的

#include <concepts>

template<typename A>
concept Num = std::constructible_from<A, int>; // simplified

template<Num A>
A poly_1() {
  return 1;
}
Run Code Online (Sandbox Code Playgroud)

现在,poly_1可用于任何需要满足该Num概念的类型的设置,即特别是以下类型:constructible_fromint但不能在需要某种其他类型的上下文中使用。

(在较旧的 C++ 中,这样的模板只是鸭子类型,即它并不明确需要设置,Num但编译器会尝试按原样使用它,然后在注意到1无法转换为指定类型。)