【问题标题】:Polyvariadic Functions in HaskellHaskell 中的多变量函数
【发布时间】:2011-01-10 12:04:24
【问题描述】:

看完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 (+)

一旦我进行了这个更改,它编译得很好,我可以在ghci 中使用collapse 函数。

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 的后果,但我不知道,我想更好地了解发生了什么。

【问题讨论】:

  • 这是您正在试验的一些非常酷的代码! Oleg Kiselyov 的那篇文章很棒,当我第一次读到它时,它完全让我大吃一惊,现在仍然如此。 :-) 我对有关包装器类型必要性的答案进行了尝试......希望它有所帮助。

标签: haskell polymorphism polyvariadic


【解决方案1】:

函数依赖背后的想法是在声明中

class Collapse a r | r -> a where
  ...

r -&gt; a 位表示ar 唯一确定。所以,你不能拥有instance Collapse (a -&gt; r) (a -&gt; r)instance Collapse a (a -&gt; r)。请注意,instance Collapse (a -&gt; r) (a -&gt; r) 来自instance Collapse a a 以获取完整图片。

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

【讨论】:

  • 太棒了!乐意效劳。 :-) 我看到 camccann 的回答非常有用。 UndecidableInstances 问题和“非函数实例”文章的链接很棒......一个非常有启发性的问题,这个!
【解决方案2】:

如果您仍在对此进行试验,这里有一个示例,它从一个获取列表的函数构造一个多变量函数,而不需要包装类型或不可判定的实例:

{-# 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"

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

【讨论】:

    【解决方案3】:

    Michał Marczyk 关于fundeps 和实例匹配问题是绝对正确的,并且包装器类型似乎很容易解决。另一方面,如果您已经在阅读 Oleg 的网站,您可能更愿意去 deeper down the rabbit hole 并尝试为“不是函数的任何类型”编写一个实例。

    就 UndecidableInstances 而言,覆盖条件描述为here;您的实例失败的原因应该很明显。请注意,这里的“undecidable”一词的含义与“Halting Problem is undecidable”的含义大致相同——也就是说,您告诉 GHC 鲁莽地尝试解决可能将其发送到无限循环的代码仅基于您认为没关系的断言。破解巧妙的想法很有趣,但同意成为 GHC 的人类首过类型检查员是我个人觉得很累的负担。

    【讨论】:

      猜你喜欢
      • 2018-02-15
      • 1970-01-01
      • 2011-03-28
      • 2016-01-24
      • 1970-01-01
      • 2017-04-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多