[英]How does polymorphic recursion relate to infinite type errors?
Context: I ran into a problem and got help here , however I realized I don't understand the solution.背景:我遇到了一个问题并在这里得到了帮助,但是我意识到我不明白解决方案。
The keyword appears to be polymorphic recursion .关键字似乎是多态递归。 I thought that
foldr
is polymorphic and recursive, but this seems to be a different concept.我认为
foldr
是多态和递归的,但这似乎是一个不同的概念。
This explicit recursion works (based on shell-monad ):这种显式递归有效(基于shell-monad ):
cmdList :: (Param command, Param arg, CmdParams result) =>
command -> [arg] -> result
cmdList command = go . reverse where
go :: (Param arg, CmdParams result) => [arg] -> result
go [] = cmd command
go (arg:args) = go args arg
But trying to refactor the answer towards my original attempts using foldr
I quickly ran into the problem I struggled with initially: infinite types.但是尝试使用
foldr
重构我最初尝试的答案时,我很快遇到了我最初遇到的问题:无限类型。 Replacing a constant cmd command
with b
:用
b
替换常量cmd command
:
go :: (Param arg, CmdParams result) => result -> [arg] -> result
go b [] = b
go b (arg:args) = go b args arg
• Occurs check: cannot construct the infinite type:
result ~ arg -> result
• In the expression: go b args arg
It seems to me that GHC no longer accepts polymorphic result
that is either a value or a function.在我看来,GHC 不再接受值或 function 的多态
result
。
Why does that happen?为什么会这样?
Can I not use foldr
in such cases?在这种情况下我不能使用
foldr
吗?
Here's a simple recursive function:这是一个简单的递归 function:
ignore :: [a] -> ()
ignore [] = ()
ignore (a:as) = ignore as
It is polymorphic, and it is recursive.它是多态的,并且是递归的。 But, like almost all recursions, whatever type the caller chooses for
a
, the recursion happens using the same choice for a
.但是,就像几乎所有递归一样,无论调用者为
a
选择什么类型,递归都会使用与 a 相同的选择a
。 That is, we could write this, with ScopedTypeVariables and probably a handful of other extensions:也就是说,我们可以使用 ScopedTypeVariables 和可能的一些其他扩展来编写这个:
ignore :: forall a. [a] -> ()
ignore (a:as) = ignore @a as
The term "polymorphic recursion" refers to a very special kind of recursion, where the recursive call chooses a different type for some parameter.术语“多态递归”是指一种非常特殊的递归,其中递归调用为某些参数选择不同的类型。 The standard example is this one:
标准示例是这个:
data BalancedTree a = Leaf a | Branch (BalancedTree (a, a))
foldBT :: (a -> a -> a) -> BalancedTree a -> a
foldBT f (Leaf a) = a
foldBT f (Branch bt) = uncurry f $ foldBT (\(x, y) (x', y') -> (f x x', f y y')) bt
(For example, foldBT (+)
would add all the elements of the tree.) (例如,
foldBT (+)
将添加树的所有元素。)
The special thing here is that the recursive call to foldBT
is not recursing over a BalancedTree a
-- but over a BalancedTree (a, a)
instead.这里的特别之处在于对
foldBT
的递归调用不是在BalancedTree a
上递归——而是在BalancedTree (a, a)
上递归。 The type has changed, like this:类型已更改,如下所示:
foldBT :: forall a. (a -> a -> a) -> BalancedTree a -> a
foldBT f (Branch bt) = uncurry f $ foldBT @(a, a) {- N.B. not a! -} (\(x, y) (x', y') -> (f x x', f y y')) bt
It turns out that type inference in the setting that allows this kind of thing is pretty hard.事实证明,允许这种事情的设置中的类型推断非常困难。 To deal with that, Haskell has a restriction: inferred types always use monomorphic recursion, that is, all recursive calls choose all the same values for the type variables involved.
为了解决这个问题,Haskell 有一个限制:推断类型总是使用单态递归,也就是说,所有递归调用为所涉及的类型变量选择所有相同的值。 You can still get polymorphic recursion by giving an appropriate type signature, because then inference is not needed.
您仍然可以通过提供适当的类型签名来获得多态递归,因为不需要推理。
Now, in your working situation, you write现在,在你的工作情况下,你写
cmdList :: forall command arg result.
(Param command, Param arg, CmdParams result) =>
command -> [arg] -> result
cmdList command = go . reverse where
-- for clarity, I have chosen fresh type variable names
go :: forall arg' result'. (Param arg', CmdParams result') => [arg'] -> result'
go [] = cmd command
go (arg:args) = go args arg
Notice that in the recursive case, go
is being given an extra argument on the right hand side of the equation compared to the left;请注意,在递归情况下,
go
在等式的右侧与左侧相比被赋予了额外的参数; This is polymorphic recursion;这是多态递归; the
result
type is slowly accumulating extra arguments. result
类型正在慢慢积累额外的 arguments。
go (arg:args) = go @arg' @(arg' -> result') {- N.B. not result'! -} args arg
This means that in the base case,这意味着在基本情况下,
go [] = cmd command
the type of cmd
may be different depending on how many arguments there are in the list, In other words: you can think of events happening in this order, first the user chooses a command type, an argument type; cmd
的类型可能会有所不同,具体取决于列表中有多少个 arguments,换句话说:您可以考虑按此顺序发生的事件,首先用户选择命令类型,参数类型; and a result type;和结果类型; then you traverse the list and choose a different result type that accepts more arguments;
然后您遍历列表并选择接受更多参数的不同结果类型; then you call
cmd
with that newly chosen result type.然后你用新选择的结果类型调用
cmd
。
Compare the version that doesn't work:比较不起作用的版本:
go :: (Param arg, CmdParams result) => result -> [arg] -> result
go b [] = b
go b (arg:args) = go b args arg
In this version, a choice has been lost;在这个版本中,一个选择已经丢失; first the user chooses a result type, then you traverse the list and choose the same result type as the user did;
首先用户选择一个结果类型,然后遍历列表并选择与用户相同的结果类型; then you use their
b
of that type.然后你使用他们的那种类型的
b
。 But this is a problem -- they may not have chosen the right number of arguments for that list, and even if they did, it's not really reasonable to expect the compiler to be able to know that statically!但这是一个问题——他们可能没有为该列表选择正确数量的 arguments,即使他们选择了,期望编译器能够静态地知道这一点也不合理!
This explains the occurs check error you've gotten: for go
to work right in this setting, its b
argument has to be able to accept an arbitrary number of arguments, and there just isn't any single type that does that.这解释了您遇到的发生检查错误:
go
在此设置中正常工作,它的b
参数必须能够接受任意数量的 arguments,并且没有任何单一类型可以做到这一点。
It also explains why you cannot use foldr
: foldr
is implemented with monomorphic recursion.它还解释了为什么不能使用
foldr
: foldr
是通过单态递归实现的。 It is probably possible to create a variant of foldr
using RankNTypes that could sit in this spot, but I suspect it's more work than it's worth.可能有可能使用 RankNTypes 创建一个
foldr
的变体,它可以放在这个位置,但我怀疑它的工作量大于它的价值。
And, by the way, I think you can make your refactoring work, though I'm not 100% because I haven't installed shell-monad to check this.而且,顺便说一句,我认为你可以让你的重构工作,虽然我不是 100% 因为我没有安装 shell-monad 来检查这个。 But it would look something like this:
但它看起来像这样:
go :: (Param arg, CmdParams result) => (forall result'. CmdParams result') -> [arg] -> result
-- same implementation as before
The key here is that putting the extra forall
there forces the first b
argument to be polymorphic;这里的关键是把额外的
forall
放在那里会强制第一个b
参数是多态的; this lets you choose result'
to be a suitably-many-arguments variant of result
when you get to the base case.当您到达基本情况时,这使您可以选择
result'
作为result
的适当多参数变体。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.