Haskell,多元函数和类型推理。

时间:2021-09-29 16:46:39

While looking for Polyvariadic function examples, I found this resource: *: How to create a polyvariadic haskell function?, and there was an answer snippet like this:

在寻找多元函数的例子时,我发现了这个资源:*:如何创建一个多变量haskell函数?,有这样一个回答片段:

class SumRes r where 
  sumOf :: Integer -> r

instance SumRes Integer where
  sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
  sumOf x = sumOf . (x +) . toInteger

Then we could use:

然后我们可以使用:

*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0  :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59

I tried out to change it a little bit, just for curiosity, because I found it pretty tricky at first glance, and I got into this:

我试着去改变它,只是出于好奇,因为我第一眼就发现它很棘手,于是我就开始了:

class SumRes r where
  sumOf :: Int -> r

instance SumRes Int where
  sumOf = id

instance (SumRes r) => SumRes (Int -> r) where
  sumOf x = sumOf . (x +)

I just changed Integer to Int and turned instance (Integral a, SumRes r) => SumRes (a -> r) where less polymorphic to instance (SumRes r) => SumRes (Int -> r) where

我只是把整数变成了整数,然后变成了实例(积分a, SumRes r) => SumRes (a -> r),在这里,不太多态性的实例(SumRes r) => SumRes (Int -> r)。

To compile it I had to set XFlexibleInstances flag. When I tryed to test sumOf function I got a problem:

为了编译它,我必须设置xflexbleinstances标志。当我尝试测试sumOf函数时,我遇到了一个问题:

*Main> sumOf 1 :: Int
1
*Main> sumOf 1 1 :: Int
<interactive>:9:9
    No instance for (Num a0) arising from the literal `1'
    The type variable `a0' is ambiguous...

Then I tried:

然后我试着:

*Main> sumOf (1 :: Int) (1 :: Int) :: Int
2

Why can't Haskell infer that we want an Int in this situation, considering we are using Int within our SumRes typeclass?

为什么Haskell不能推断在这种情况下我们需要一个Int类型,因为我们在SumRes typeclass中使用Int ?

2 个解决方案

#1


4  

The instance

实例

instance (...) => SumRes (Int -> r) where

roughly means "here's how to define SumRes on Int -> r for any r (under certain conditions)". Compare it with

大致意思是“这里是如何定义对任何r(在特定条件下)的积分-> r”。比较它与

instance (...) => SumRes (a -> r) where

which means "here's how to define SumRes on a -> r for any a,r (under certain conditions)".

这意味着“这里是如何定义在一个-> r上的任何a,r(在一定条件下)”。

The main difference is that the second one states that this is the relevant instance whichever the types a,r might be. Barring some (very tricky and potentially dangerous) Haskell extension, one can not add more instances later on involving functions. Instead, the first one leaves room for new instances such as e.g.

主要的区别是,第二种情况是,这是相关的实例,无论哪种类型,r可能是。除非有一些(非常棘手和潜在危险的)Haskell扩展,否则不能在以后添加更多的包含函数的实例。相反,第一个为新实例留出了空间,例如。

instance (...) => SumRes (Double -> r) where ...
instance (...) => SumRes (Integer -> r) where ...
instance (...) => SumRes (Float -> r) where ...
instance (...) => SumRes (String -> r) where ... -- nonsense, but allowed

This is paired with the fact that numeric literals such as 5 are polymorphic: their type must be inferred from the context. Since later on the compiler might find e.g. a Double -> r instance and choose Double as the literal types, the compiler does not commit to the Int -> r instance, and reports the ambiguity in a type error.

这与数字文字(例如5)是多态的这一事实结合在一起:它们的类型必须从上下文推断出来。此后,编译器可能会发现,例如Double -> r实例,并选择Double作为文字类型,编译器不提交Int -> r实例,并报告类型错误中的歧义。

Note that, using some (safe) Haskell extensions (such as TypeFamilies), it is possible to "promise" to the compiler that your Int -> r is the only one that will be declared in the whole program. This is done like this:

注意,使用一些(安全的)Haskell扩展(例如TypeFamilies),可以向编译器“承诺”,您的Int -> r是唯一一个在整个程序中声明的。这是这样做的:

instance (..., a ~ Int) => SumRes (a -> r) where ...

This promises to handle all the "functional type" cases, but requires that a is actually the same type as Int.

这将处理所有的“功能类型”案例,但要求a实际上与Int类型相同。

#2


3  

Number literals are themselves polymorphic, rather than being of type Int

数字文字本身是多态的,而不是Int型的。

*Main> :t 1
1 :: Num a => a

Take a look what happens when we get the type signature:

看看当我们得到类型签名时会发生什么:

*Main> :t sumOf 1 2 3
sumOf 1 2 3 :: (Num a, Num a1, SumRes (a -> a1 -> t)) => t

Notice that the type does not mention Int at all. The type checker can't figure out how to actually compute the sum because none of the defined Int instances are general enough to apply here.

注意,类型根本没有提到Int。类型检查器不知道如何实际计算求和,因为定义的Int实例都不适用于此。

If you fix the types to be Int, then you end up with

如果将类型固定为Int,那么结果就是。

*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int)
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: SumRes t => t

*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int

Note that SumRes t => t is compatible with Int because we have a SumRes Int instance, but if we don't explicitly specify Int, then we have no instances general enough to apply here, because there is no general SumRes t instance.

注意,SumRes t => t与Int兼容,因为我们有一个SumRes Int实例,但是如果我们没有显式地指定Int,那么我们就没有足够的实例在这里应用,因为没有general SumRes t实例。

#1


4  

The instance

实例

instance (...) => SumRes (Int -> r) where

roughly means "here's how to define SumRes on Int -> r for any r (under certain conditions)". Compare it with

大致意思是“这里是如何定义对任何r(在特定条件下)的积分-> r”。比较它与

instance (...) => SumRes (a -> r) where

which means "here's how to define SumRes on a -> r for any a,r (under certain conditions)".

这意味着“这里是如何定义在一个-> r上的任何a,r(在一定条件下)”。

The main difference is that the second one states that this is the relevant instance whichever the types a,r might be. Barring some (very tricky and potentially dangerous) Haskell extension, one can not add more instances later on involving functions. Instead, the first one leaves room for new instances such as e.g.

主要的区别是,第二种情况是,这是相关的实例,无论哪种类型,r可能是。除非有一些(非常棘手和潜在危险的)Haskell扩展,否则不能在以后添加更多的包含函数的实例。相反,第一个为新实例留出了空间,例如。

instance (...) => SumRes (Double -> r) where ...
instance (...) => SumRes (Integer -> r) where ...
instance (...) => SumRes (Float -> r) where ...
instance (...) => SumRes (String -> r) where ... -- nonsense, but allowed

This is paired with the fact that numeric literals such as 5 are polymorphic: their type must be inferred from the context. Since later on the compiler might find e.g. a Double -> r instance and choose Double as the literal types, the compiler does not commit to the Int -> r instance, and reports the ambiguity in a type error.

这与数字文字(例如5)是多态的这一事实结合在一起:它们的类型必须从上下文推断出来。此后,编译器可能会发现,例如Double -> r实例,并选择Double作为文字类型,编译器不提交Int -> r实例,并报告类型错误中的歧义。

Note that, using some (safe) Haskell extensions (such as TypeFamilies), it is possible to "promise" to the compiler that your Int -> r is the only one that will be declared in the whole program. This is done like this:

注意,使用一些(安全的)Haskell扩展(例如TypeFamilies),可以向编译器“承诺”,您的Int -> r是唯一一个在整个程序中声明的。这是这样做的:

instance (..., a ~ Int) => SumRes (a -> r) where ...

This promises to handle all the "functional type" cases, but requires that a is actually the same type as Int.

这将处理所有的“功能类型”案例,但要求a实际上与Int类型相同。

#2


3  

Number literals are themselves polymorphic, rather than being of type Int

数字文字本身是多态的,而不是Int型的。

*Main> :t 1
1 :: Num a => a

Take a look what happens when we get the type signature:

看看当我们得到类型签名时会发生什么:

*Main> :t sumOf 1 2 3
sumOf 1 2 3 :: (Num a, Num a1, SumRes (a -> a1 -> t)) => t

Notice that the type does not mention Int at all. The type checker can't figure out how to actually compute the sum because none of the defined Int instances are general enough to apply here.

注意,类型根本没有提到Int。类型检查器不知道如何实际计算求和,因为定义的Int实例都不适用于此。

If you fix the types to be Int, then you end up with

如果将类型固定为Int,那么结果就是。

*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int)
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: SumRes t => t

*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int

Note that SumRes t => t is compatible with Int because we have a SumRes Int instance, but if we don't explicitly specify Int, then we have no instances general enough to apply here, because there is no general SumRes t instance.

注意,SumRes t => t与Int兼容,因为我们有一个SumRes Int实例,但是如果我们没有显式地指定Int,那么我们就没有足够的实例在这里应用,因为没有general SumRes t实例。