3

在寻找多变量函数示例时,我发现了这个资源: StackOverflow:如何创建多变量 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

然后我们可以使用:

*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

出于好奇,我尝试对其进行了一些更改,因为乍一看我发现它非常棘手,因此我进入了这个:

class SumRes r where
  sumOf :: Int -> r

instance SumRes Int where
  sumOf = id

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

我只是更改IntegerIntinstance (Integral a, SumRes r) => SumRes (a -> r) where减少了多态性instance (SumRes r) => SumRes (Int -> r) where

要编译它,我必须设置XFlexibleInstances标志。当我尝试测试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...

然后我尝试了:

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

考虑到我们在类型类中使用,为什么 Haskell 不能Int在这种情况下推断出我们想要一个?IntSumRes

4

2 回答 2

5

实例

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

大致意思是“这里是如何定义任何(SumRes在某些条件下)”。将其与Int -> rr

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

这意味着“这是为任何(在某些条件下)定义的方法” SumResa -> ra,r

主要区别在于第二个声明这是相关实例,无论类型a,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

这与诸如5多态之类的数字文字的事实配对:它们的类型必须从上下文中推断出来。由于稍后编译器可能会找到例如一个Double -> r实例并选择Double作为文字类型,因此编译器不会提交该Int -> r实例,并在类型错误中报告歧义。

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

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

这承诺处理所有“功能类型”的情况,但要求它a实际上与Int.

于 2015-10-25T19:55:52.750 回答
3

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

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

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

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

请注意,该类型根本没有提及Int。类型检查器无法弄清楚如何实际计算总和,因为定义的Int实例都不够通用,无法在此处应用。

如果您将类型固定为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

请注意,这SumRes t => t是兼容的,Int因为我们有一个SumRes Int实例,但如果我们没有明确指定Int,那么我们没有足够通用的实例来应用这里,因为没有通用SumRes t实例。

于 2015-10-25T19:14:14.087 回答