Haskell 中的多变量函数
阅读这篇关于在 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 (+)
一旦我进行了此更改,它就可以正常编译,并且我可以使用 collapseghci
中的 code> 函数。
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 的后果,但我不知道,而且我想更好地了解发生了什么。
After reading this article on writing polyvariadic functions in Haskell, I tried to write some of my own.
At first I thought I'd try to generalize it - so I could have a function that returned variadic functions by collapsing arguments as given.
{-# 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')
However, the compiler didn't like that:
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
If I went back and added a wrapper type for the final result, however, it worked:
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 (+)
Once I made this change, it compiled fine, and I could use the collapse
function in ghci
.
ghci> let C s = Collapse.sum 1 2 3 in s
6
I'm not sure why the wrapper type is required for the final result. If anyone could explain that, I'd highly appreciate it. I can see that the compiler's telling me that it's some issue with the functional dependencies, but I don't really grok the proper use of fundeps yet.
Later, I tried to take a different tack, and try and define a variadic function generator for functions that took a list and returned a value. I had to do the same container trick, and also allow 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)
Without allowing UndecidableInstances
the compiler complained that my instance declarations were illegal:
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)'
However, once it compiled, I could successfully use it in 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
I guess what I'm looking for is an explanation of why the container type for the final value is necessary, as well as why all the various functional dependencies are necessary.
Also, this seemed odd:
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
I'm guessing that's fallout from allowing UndecidableInstances
, but I don't know, and I'd like to better understand what's going on.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
Michał Marczyk 关于fundeps 和实例匹配问题的说法绝对正确,并且包装器类型似乎是一个简单的解决方案。另一方面,如果您已经在阅读 Oleg 的网站,您可能更愿意访问 深入研究兔子洞并尝试为“任何不是函数的类型”编写一个实例。
就 UndecidableInstances 而言,描述了覆盖条件 此处;您的实例失败的原因应该很明显。请注意,这里的“不可判定”一词与“停止问题是不可判定的”含义大致相同,也就是说,您告诉 GHC 不计后果地尝试解析可能会使其进入无限循环的代码仅基于您的断言“没问题”。破解巧妙的想法很有趣,但同意成为 GHC 的人类首次类型检查员是我个人认为令人厌倦的负担。
Michał Marczyk is absolutely correct about the fundeps and instance matching issue, and the wrapper type seems like an easy fix. On the other hand, if you're already reading Oleg's site, you might prefer to go deeper down the rabbit hole and try writing an instance for "any type that isn't a function".
As far as UndecidableInstances goes, the coverage condition is described here; it should be obvious why your instances fail it. Note that the word "undecidable" here means undecidable in roughly the same sense as in "the Halting Problem is undecidable"--that is to say, you're telling GHC to recklessly attempt to resolve code that could send it into an infinite loop based only on your assertion that it's okay. It's fun for hacking neat ideas, but consenting to be a human first-pass type-checker for GHC is a burden I personally find wearying.
函数依赖背后的想法是,在像
r -> 这样的声明中a
位表示a
由r
唯一确定。因此,您不能拥有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)
。现在这是您可以拥有的第二个类型参数等于(a -> r)
的唯一Collapse
实例,因为类声明表示第一个类型参数可以从第二个类型参数推导。接下来,您尝试建立实例 a (a -> r)
,这将添加另一个Collapse
实例,第二个类型参数为(a -> r) ; r)
。因此,GHC 抱怨道。The idea behind functional dependencies is that in a declaration like
the
r -> a
bit says thata
is uniquely determined byr
. So, you can't haveinstance Collapse (a -> r) (a -> r)
andinstance Collapse a (a -> r)
. Note thatinstance Collapse (a -> r) (a -> r)
follows frominstance Collapse a a
for the complete picture.In other words, your code is trying to establish
instance Collapse t t
(the type variable's name is of course unimportant) andinstance Collapse a (a -> r)
. If you substitute(a -> r)
fort
in the first instance declaration, you getinstance Collapse (a -> r) (a -> r)
. Now this is the only instance ofCollapse
with the second type parameter equal to(a -> r)
that you can have, because the class declaration says that the first type parameter is to be deducible from the second. Yet next you try to establishinstance a (a -> r)
, which would add another instance ofCollapse
with the second type parameter being(a -> r)
. Thus, GHC complains.如果您仍在尝试这一点,这里有一个从采用列表的函数构造多变量函数的示例,而不需要包装器类型或不可判定的实例:
这种方法的缺点是类型检查器必须能够推断出结果的类型(或者你必须对其进行注释),并且它在多态数字常量上严重失败;您提到的文章中讨论了这两个问题的原因。不知道您是否会发现这有帮助,但我之前正在修改多变量函数并记住了这个问题。
If you're still experimenting with this, here's an example of constructing a polyvariadic function from a function taking a list, without requiring either a wrapper type or undecidable instances:
The downsides to this approach are that the type checker must be able to infer the type of the result (or you have to annotate it), and that it fails badly on polymorphic numeric constants; the reasons for both problems are discussed in the article you mentioned. Don't know if you'll find that helpful, but I was tinkering with polyvariadic functions earlier and remembered this question.