haskell 将受约束的文字赋给多态变量

zlhcx6iw  于 2022-11-14  发布在  其他
关注(0)|答案(2)|浏览(147)

在学习haskell与Haskell Programming from first principles的关系时,我发现了一个让我困惑的练习。
以下是简短版本:

For the following definition:
   a) i :: Num a => a
      i = 1
   b) Try replacing the type signature with the following:
      i :: a

替换给我一个错误:

error:
    • No instance for (Num a) arising from the literal ‘1’
      Possible fix:
        add (Num a) to the context of
          the type signature for:
            i' :: forall a. a
    • In the expression: 1
      In an equation for ‘i'’: i' = 1
   |
38 | i' = 1
   |      ^

我或多或少地清楚Num约束是如何产生的。不清楚的是为什么将1赋给多态变量i'会产生错误。
为什么这样做:

id 1

而这一个则没有:

i' :: a
i' = 1
id i'

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

ffscu2ro

ffscu2ro1#

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

class Object {};

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

class Int: Num { int i; ... };

然后你就可以用Int值作为参数给一个需要Num参数的函数,或者用Num值作为参数给一个需要Object参数的函数。
但是,这根本不是Haskell的类型类的工作方式。Num不是一个值类(就像上面的例子中,它是属于某个子类的所有值的类),而是表示特定数字风格的所有 types 的类。
这有什么不同呢?像1 :: Num a => a这样的多态文字不会生成一个特定的Num值,然后将其上转换为一个更通用的类。相反,它期望调用者首先选择一个具体的类型,在其中呈现数字,然后立即在该类型中生成数字,此后该类型永远不会改变。
换句话说,多态值有一个隐式类型级参数。
1.应该使用什么类型a是明确的。(它不一定需要在这里 * 固定 *:调用者本身也可以是一个多态函数。)
1.编译器可以证明此类型a有一个Num示例。
在C++中,Haskell类型类/多态文字的类似物不是[sub]类和它们的对象,而是被约束到一个概念的 * 模板 *:

#include <concepts>

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

template<Num A>
A poly_1() {
  return 1;
}

现在,poly_1可以用在任何需要满足Num概念的类型的设置中,即特别是constructible_fromint的类型,但不能用在需要一些其他类型的上下文中。
(In旧的C++这样的模板只是duck类型的,也就是说,它没有明确要求Num设置,但编译器会尝试按原样使用它,然后在注意到1无法转换为指定类型时给予类型错误。)

mkh04yzy

mkh04yzy2#

tl;dr

声明为i' :: a的值i'必须能够替换任何其他值,无一例外。1不是这样的值,因为它不能被使用,比如说,在需要String的地方,只是举一个例子。

较长版本

让我们从一个不太有争议的场景开始,在这个场景中,您确实需要类型约束:

plus :: a -> a -> a
plus x y = x + y

这不会编译,因为签名等价于plus :: forall a. a -> a -> a,而且RHS x + y对于xy所在的任何公共类型a都是有意义的,这显然是不正确的。因此,您可以通过提供一个约束来解决上述问题,该约束保证+在两个a之间是可能的,并且可以通过将Num a =>放在::之后(或者甚至放弃多态类型而将a改为Int)来实现。
但是,有些函数不需要对参数进行任何约束,下面是其中的三个:

id :: a -> a
id x = x

const :: a -> b -> a
const x _ = x

Data.Tuple.swap :: (a, b) -> (b, a)
Data.Tuple.swap (a, b) = (b, a)

你可以向这些函数传递任何东西,它们总是能工作的,因为它们的定义没有对这些对象做任何假设,因为它们只是把它们洗牌/丢弃。
同样地,

i' :: a
i' = 1

无法编译,因为1不能表示任何类型的值a。例如,它不能表示String,而签名i' :: a表达的思想是,可以将i'放在任何位置,例如,需要Int的位置,以及需要泛型Num的位置,或者在期望String的情况下等等。
换句话说,上面的签名表示您可以在这两个语句中使用i'

j = i' + 1
k = i' ++ "str"

所以问题是:就像我们发现一些函数的签名不以任何方式约束它们的参数一样,我们是否有一个值存在于你能想到的每一个类型中?
是的,有一些类似的值,这里有两个:

i' :: a
i' = error ""

j' :: a
j' = undefined

它们都是“bottoms”,或⊥。
(¹)我所说的“可用”是指当你在代码编译的地方编写代码时,代码会继续编译。

相关问题