【问题标题】:Haskell, polyvariadic function and type inferenceHaskell,多变量函数和类型推断
【发布时间】:2016-01-24 20:03:59
【问题描述】:

在寻找多变量函数示例时,我发现了这个资源: StackOverflow: How to create a polyvariadic haskell function?,有这样一个回答sn-p:

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?

【问题讨论】:

  • 看起来默认(假设1 表示Int 1)在ghci 中的实例解析后启动。没错。您可以为 Integer 添加一组单独的 step-case 实例而不是 Int,然后您就会有一个真正的歧义。
  • 您可能想阅读this question, and my answer to it 以了解另一个方向的一些解释,以及一个可以用来处理此处错误类型推断的有用技巧:使用instance a ~ Int =&gt; SumRes (a -&gt; r) 而不是instance SumRes (Int -&gt; r)。
  • 为了澄清关于默认的部分,Int 不是通常默认的类型,但Integer 是。因此,原始代码通过将所有文字默认为 Integer 来工作。
  • 我的评论有点天真,因为即使在更改的代码中放置Integer 而不是Int 也不足以修复它。 (刚刚发布的duplicate question 让我意识到了这一点。)

标签: haskell recursion typeclass polyvariadic


【解决方案1】:

实例

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

大致意思是“这里是如何在Int -&gt; r 上为任何r 定义SumRes(在某些条件下)”。比较一下

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

这意味着“这里是如何在a -&gt; r 上为任何a,r 定义SumRes(在某些条件下)”。

主要区别在于第二个声明这是 相关实例,无论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 -&gt; r 实例并选择Double 作为文字类型,编译器不会提交Int -&gt; r 实例,并在类型错误中报告歧义。

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

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

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

【讨论】:

  • 这正是重点。当您说here's how to define SumRes on Int -&gt; r for any r (在某些条件下)时,这意味着虽然我们在这种特定情况下向编译器解释,但可能是其他方式喜欢Double -&gt; r。另一个问题:为什么TypeFamilies 不是默认的?有充分的理由吗?
  • @FtheBuilder 除了一些值得注意的例外,GHC 默认尝试遵循the Report,并且类型系列不在报告中。 GHC 可能是唯一支持它们的实现;并且由于支持它们可能被视为重大的实施负担,我怀疑它们在不久的将来不会成为报告的一部分。但是,我不会认为TypeFamilies 是一个有争议或“恶心”的扩展;如果你喜欢它,并打算使用 GHC,就可以不受惩罚地使用它。
【解决方案2】:

数字字面量本身是多态的,而不是 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 =&gt; t 与Int 兼容,因为我们有一个SumRes Int 实例,但如果我们没有明确指定Int,那么我们没有足够通用的实例来应用这里,因为没有一般SumRes t实例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-15
    • 1970-01-01
    相关资源
    最近更新 更多