Haskell、多元函数与类型推断

Haskell, polyvariadic function and type inference

在寻找多元函数示例时,我找到了这个资源: Whosebug: How to create a polyvariadic haskell function?,并且有一个这样的答案片段:

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 +)

我刚刚将 Integer 更改为 Int,并将 instance (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

考虑到我们在 SumRes 类型类中使用 Int,为什么不能 Haskell 推断我们在这种情况下需要 Int

数字文字本身是多态的,而不是 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 个实例中的 none 个足够通用,可以在此处应用。

如果您将类型固定为 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 => tInt 兼容,因为我们有一个 SumRes Int 实例,但是如果我们没有明确指定 Int,那么我们就没有实例general 够用到这里,因为没有general SumRes t instance.

实例

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

大致意思是 "here's how to define SumRes on Int -> r for any r (under certain conditions)"。与

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

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

主要区别在于第二个声明这是 相关实例,无论 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),可以 "promise" 编译器认为您的 Int -> r 是只有一个将在整个程序中声明。这是这样做的:

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

这承诺处理所有 "functional type" 情况,但要求 a 实际上与 Int.

是同一类型