Haskell中的多变量函数

时间:2010-01-28 16:45:14

标签: haskell polymorphism polyvariadic

在阅读this article on writing polyvariadic functions in Haskell之后,我试着写一些自己的。

起初我以为我会尝试概括它 - 所以我可以通过折叠参数给出一个返回可变参数函数的函数。

{-# OPTIONS -fglasgow-exts #-}
module Collapse where
class Collapse a r | r -> a where
  collapse :: (a -> a -> a) -> a -> r
instance Collapse a a where
  collapse _ = id
instance (Collapse a r) => Collapse a (a -> r) where
  collapse f a a' = collapse f (f a a')

然而,编译器并不喜欢这样:

Collapse.hs:5:9:
    Functional dependencies conflict between instance declarations:
      instance Collapse a a -- Defined at Collapse.hs:5:9-20
      instance (Collapse a r) => Collapse a (a -> r)
        -- Defined at Collapse.hs:7:9-43

如果我回去并为最终结果添加了包装类型,那么它可以工作:

module Collapse where
class Collapse a r | r -> a where
  collapse :: (a -> a -> a) -> a -> r
data C a = C a
instance Collapse a (C a) where
  collapse _ = C . id
instance (Collapse a r) => Collapse a (a -> r) where
  collapse f a a' = collapse f (f a a')
sum :: (Num a, Collapse a r) => a -> r
sum = collapse (+)

我做了这个更改后,编译得很好,我可以使用collapse中的ghci函数。

ghci> let C s = Collapse.sum 1 2 3 in s
6

我不确定为什么最终结果需要包装类型。如果有人能够解释,我会非常感激。我可以看到编译器告诉我这是函数依赖的一些问题,但我还没有真正理解正确使用fundeps。

后来,我试图采取不同的方法,并尝试为获取列表并返回值的函数定义一个可变函数生成器。我不得不做同样的容器技巧,也允许UndecidableInstances

{-# OPTIONS -fglasgow-exts #-}
{-# LANGUAGE UndecidableInstances #-}
module Variadic where
class Variadic a b r | r -> a, r -> b where
  variadic :: ([a] -> b) -> r
data V a = V a
instance Variadic a b (V b) where
  variadic f = V $ f []
instance (Variadic a b r) => Variadic a b (a -> r) where
  variadic f a = variadic (f . (a:))
list :: Variadic a [a] r => r
list = variadic . id
foldl :: (Variadic b a r) => (a -> b -> a) -> a -> r
foldl f a = variadic (Prelude.foldl f a)

不允许UndecidableInstances编译器抱怨我的实例声明是非法的:

Variadic.hs:7:0:
    Illegal instance declaration for `Variadic a b (V b)'
        (the Coverage Condition fails for one of the functional dependencies;
         Use -XUndecidableInstances to permit this)
    In the instance declaration for `Variadic a b (V b)'

Variadic.hs:9:0:
    Illegal instance declaration for `Variadic a b (a -> r)'
        (the Coverage Condition fails for one of the functional dependencies;
         Use -XUndecidableInstances to permit this)
    In the instance declaration for `Variadic a b (a -> r)'

然而,一旦编译完成,我就可以在ghci中成功使用它:

ghci> let V l = Variadic.list 1 2 3 in l
[1,2,3]
ghci> let vall p = Variadic.foldl (\b a -> b && (p a)) True
ghci> :t vall
vall :: (Variadic b Bool r) => (b -> Bool) -> r
ghci> let V b = vall (>0) 1 2 3 in b
True

我想我正在寻找的是解释为什么最终值的容器类型是必要的,以及为什么所有各种功能依赖都是必要的

此外,这似乎很奇怪:

ghci> let vsum = Variadic.foldl (+) 0

<interactive>:1:10:
    Ambiguous type variables `a', `r' in the constraint:
      `Variadic a a r'
        arising from a use of `Variadic.foldl' at <interactive>:1:10-29
    Probable fix: add a type signature that fixes these type variable(s)

<interactive>:1:10:
    Ambiguous type variable `a'in the constraint:
      `Num a' arising from the literal `0' at <interactive>:1:29
    Probable fix: add a type signature that fixes these type variable(s)
ghci> let vsum' = Variadic.foldl (+) 
ghci> :t vsum'
(Num a, Variadic a a r) => t -> a -> r
ghci> :t vsum' 0
(Num a, Variadic a a r) => a -> r
ghci> let V s = vsum' 0 1 2 3 in s
6

我猜这是允许UndecidableInstances的后果,但我不知道,我想更好地了解正在发生的事情。

3 个答案:

答案 0 :(得分:8)

功能依赖性背后的想法是在像

这样的声明中
class Collapse a r | r -> a where
  ...

r -> a位表示ar唯一确定。因此,您不能拥有instance Collapse (a -> r) (a -> r)instance Collapse a (a -> r)。请注意,instance Collapse (a -> r) (a -> r)跟随instance Collapse a a后面的完整图片。

换句话说,您的代码正在尝试建立instance Collapse t t(类型变量的名称当然不重要)和instance Collapse a (a -> r)。如果您在第一个实例声明中将(a -> r)替换为t,则会获得instance Collapse (a -> r) (a -> r)。现在这是Collapse的唯一实例,第二个类型参数等于(a -> r),你可以拥有,因为类声明说第一个类型参数是可以推导出来的从第二个。然后,您尝试建立instance a (a -> r),这将添加另一个Collapse实例,第二个类型参数为(a -> r)。因此,GHC抱怨。

答案 1 :(得分:5)

如果你还在试验这个,这里是一个从一个列表函数构造一个多变量函数的例子,不需要包装器类型或不可判定的实例:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}

class Variadic a b r | r -> a where
    variadic :: ([a] -> b) -> r

instance Variadic a b (a -> b) where
    variadic f x = f [x]

instance (Variadic a b (a -> r)) => Variadic a b (a -> a -> r) where
    variadic f x y = variadic (f . (x:)) y

vList :: (Variadic a [a] r) => r
vList = variadic id

vFoldl :: (Variadic b a r) => (a -> b -> a) -> a -> r
vFoldl f z = variadic (foldl f z)

vConcat :: (Variadic [a] [a] r) => r
vConcat = vFoldl (++) []

main = do
    putStrLn $ vConcat "abc" "def" "ghi" "jkl"
    putStrLn $ vList 'x' 'y' 'z'
    if vFoldl (&&) True True True True then putStrLn "Yes" else putStrLn "No"
    if vFoldl (&&) True True False True then putStrLn "Yes" else putStrLn "No"

这种方法的缺点是类型检查器必须能够推断结果的类型(或者你必须对其进行注释),并且它在多态数字常量上会严重失败;您提到的文章中讨论了这两个问题的原因。不知道你是否会觉得有帮助,但我之前正在修补多变量函数,并记住了这个问题。

答案 2 :(得分:4)

MichałMarczyk对于fundeps和实例匹配问题绝对正确,而包装器类型似乎很简单。另一方面,如果您已经阅读了Oleg的网站,您可能更愿意转到deeper down the rabbit hole并尝试为“不是某个功能的任何类型”编写实例。

就UndecidableInstances而言,覆盖条件描述为here;你的实例失败的原因应该是显而易见的。请注意,这里的“不可判定”一词意味着与“停止问题是不可判定的”大致相同的意义上的不可判断性 - 也就是说,你告诉GHC肆无忌惮地尝试解决可能将其发送到无限循环的代码仅基于你的断言,它是可以的。黑客攻击整齐的想法很有趣,但同意成为GHC的人类首过类型检查员是我个人觉得疲惫不堪的负担。