简体   繁体   English

多态递归与无限类型错误有何关系?

[英]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

  1. Why does that happen?为什么会这样?

  2. 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.它还解释了为什么不能使用foldrfoldr是通过单态递归实现的。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM