模式绑定:为什么允许递归模式?

Ant*_*ntC -2 haskell pattern-matching

Prelude> let (Just x) = Just (x - 1) in x
*** Exception: <<loop>>
Run Code Online (Sandbox Code Playgroud)

为什么编译器接受那个 decl?它可以在语法上告诉它绑定到循环(?)

GHC 知道:

Prelude> :set -XBangPatterns
Prelude> let !(Just x) = Just (x - 1) in x
    Recursive bang-pattern or unboxed-tuple bindings aren't allowed:
Run Code Online (Sandbox Code Playgroud)

[ ExceptionGHCi 7.10.2 上的那个和编译器失败消息。在 GHC 8.10 没有有用的消息,只是循环。]

递归模式绑定是否有一些合理的用途?我认为对于模式绑定 decl,lhs 的自由变量应该与 rhs(?)

我所说的“模式绑定”是指根据 Haskell 语言报告 2010 的第 4.4.3.2 节,特别是从数据构造函数/最外层范围开始的 lhs。

(我希望有比 lhs 上的单个 var 更令人兴奋的东西。这仍然算作“模式绑定”,请参阅评论。)

当然对于函数decls,编译器必须允许递归,因为它无法从语法上判断函数是否会循环:

Prelude> let { f 0 = 0;
               f x = f (x - 1) }           -- will terminate (for positive inputs)
             in f 5
Run Code Online (Sandbox Code Playgroud)

在我看来,lhs 上的裸变量更像是 niladic 函数而不是实际模式。

ama*_*loy 5

数据可以像 Haskell 中的函数一样懒惰。因此,递归绑定肯定有用例。例如,考虑以下定义fix

fix :: (a -> a) -> a
fix f = let x = f x in x

twoOnes :: [Int]
twoOnes = take 2 $ fix (1 :)
Run Code Online (Sandbox Code Playgroud)

请注意,由于 let 绑定的右侧在左侧是非严格的,因此绑定可以产生结果。

考虑到一些同样愚蠢的Num情况,即使是你原来的、“明显愚蠢”的表达也可能会终止:

instance Num () where
  fromInteger x = ()
  x - y = ()

loop :: ()
loop = let (Just x) = Just (x - 1) in x
Run Code Online (Sandbox Code Playgroud)

显然对于这种特定类型不是非常有用,但 GHC 不负责决定哪些法律表达式是有用的,而且通过更多的工作,可以想象更多有用的 Num 实例可能会在此处终止。

  • @AntC 你误读了这份报告。“函数绑定”有 1 个或多个参数,而不是 0。并且 `x` 是一种最简单的模式,因此 `let x = 5` 是一个模式绑定,而不是函数绑定。不存在尼拉迪函数这样的东西。 (8认同)
  • “fix”函数不是递归的。只有其中的let-绑定(非函数)才是。另外,你在对我的答案的评论中表现得异常咄咄逼人,尤其是对卡斯滕删除的答案(其中*还*具有递归非函数“fibs”)。在寻求帮助时考虑更加礼貌,以获得更好的结果。 (6认同)
  • @AntC我在Q.`p = e`下的[评论](/sf/ask/4741347131/#comment119736235_67733530)中给出了相关引用是一个_pattern_绑定;`let x = fx` 是无可辩驳的模式 `x` 的 _pattern_ 绑定,就像 @amalloy 上面所说的那样。卡斯滕的答案中“fibs”的绑定是一个 _pattern_ 绑定,所以你的评论实际上是错误的。至于“光顾”,我们实在无从得知提问者对Haskell各个方面的理解程度,而且读者范围也大于1。 (2认同)
  • @AntC 比 1 (提问者)更宽,所以它根本不是居高临下的。:) (2认同)

Nou*_*are 5

有关使用带有构造函数的递归模式而不仅仅是单个变量的有用代码的示例,请参阅此广度优先重新标记函数:

data Tree a
  = Leaf a
  | Node (Tree a) (Tree a)
  deriving Show

bfl :: forall a b. [b] -> Tree a -> Tree b
bfl l t = result
  where
    go :: Tree a -> [[b]] -> ([[b]], Tree b)
    go (Node l r) (xs:xss0) = 
      let (xss1, l') = go l xss0
          (xss2, r') = go r xss1
      in (xs:xss2, Node l' r')
    go (Leaf _) ((x:xs):xss) = (xs:xss, Leaf x)
    (xss, result) = go t (l:xss)
Run Code Online (Sandbox Code Playgroud)

在最后一行,我们使用了一个元组(xss, result),它是通过将xss自身传递给go辅助函数来定义的。这对于将树的每个级别的结果列表传递到下一个级别是必要的。