繁体   English   中英

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

[英]How does polymorphic recursion relate to infinite type errors?

背景:我遇到了一个问题并在这里得到了帮助,但是我意识到我不明白解决方案。

关键字似乎是多态递归 我认为foldr是多态和递归的,但这似乎是一个不同的概念。

这种显式递归有效(基于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

但是尝试使用foldr重构我最初尝试的答案时,我很快遇到了我最初遇到的问题:无限类型。 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

在我看来,GHC 不再接受值或 function 的多态result

  1. 为什么会这样?

  2. 在这种情况下我不能使用foldr吗?

这是一个简单的递归 function:

ignore :: [a] -> ()
ignore [] = ()
ignore (a:as) = ignore as

它是多态的,并且是递归的。 但是,就像几乎所有递归一样,无论调用者为a选择什么类型,递归都会使用与 a 相同的选择a 也就是说,我们可以使用 ScopedTypeVariables 和可能的一些其他扩展来编写这个:

ignore :: forall a. [a] -> ()
ignore (a:as) = ignore @a as

术语“多态递归”是指一种非常特殊的递归,其中递归调用为某些参数选择不同的类型。 标准示例是这个:

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

(例如, foldBT (+)将添加树的所有元素。)

这里的特别之处在于对foldBT的递归调用不是BalancedTree a上递归——而是在BalancedTree (a, a)上递归。 类型已更改,如下所示:

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

事实证明,允许这种事情的设置中的类型推断非常困难。 为了解决这个问题,Haskell 有一个限制:推断类型总是使用单态递归,也就是说,所有递归调用为所涉及的类型变量选择所有相同的值。 您仍然可以通过提供适当的类型签名来获得多态递归,因为不需要推理。

现在,在你的工作情况下,你写

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

请注意,在递归情况下, go在等式的右侧与左侧相比被赋予了额外的参数; 这是多态递归; result类型正在慢慢积累额外的 arguments。

go (arg:args) = go @arg' @(arg' -> result') {- N.B. not result'! -} args arg

这意味着在基本情况下,

go [] = cmd command

cmd的类型可能会有所不同,具体取决于列表中有多少个 arguments,换句话说:您可以考虑按此顺序发生的事件,首先用户选择命令类型,参数类型; 和结果类型; 然后您遍历列表并选择接受更多参数的不同结果类型; 然后你用新选择的结果类型调用cmd

比较不起作用的版本:

go :: (Param arg, CmdParams result) => result -> [arg] -> result
go b [] = b
go b (arg:args) = go b args arg

在这个版本中,一个选择已经丢失; 首先用户选择一个结果类型,然后遍历列表并选择与用户相同的结果类型; 然后你使用他们的那种类型的b 但这是一个问题——他们可能没有为该列表选择正确数量的 arguments,即使他们选择了,期望编译器能够静态地知道这一点也不合理!

这解释了您遇到的发生检查错误: go在此设置中正常工作,它的b参数必须能够接受任意数量的 arguments,并且没有任何单一类型可以做到这一点。

它还解释了为什么不能使用foldrfoldr是通过单态递归实现的。 可能有可能使用 RankNTypes 创建一个foldr的变体,它可以放在这个位置,但我怀疑它的工作量大于它的价值。

而且,顺便说一句,我认为你可以让你的重构工作,虽然我不是 100% 因为我没有安装 shell-monad 来检查这个。 但它看起来像这样:

go :: (Param arg, CmdParams result) => (forall result'. CmdParams result') -> [arg] -> result
-- same implementation as before

这里的关键是把额外的forall放在那里会强制第一个b参数是多态的; 当您到达基本情况时,这使您可以选择result'作为result的适当多参数变体。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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