Scala中的匿名递归函数

46
有没有一种方法可以在Scala中编写递归的匿名函数?我想的是像这样的东西:
((t: Tree) => {
    print(t.value);
    for (c <- t.children)
        thisMethod(c)
})(root)

(相关问题:哪些语言支持递归函数文字/匿名函数?)

这是一个关于IT技术的问题,与递归函数和匿名函数有关。请参考上述链接获取更多信息。
7个回答

54

如您所提供的链接中所述,您可以使用Y组合器。以下是示例:

scala> def fix[A,B](f: (A=>B)=>(A=>B)): A=>B = f(fix(f))(_)
fix: [A,B](f: ((A) => B) => (A) => B)(A) => B

scala> val fact = fix[Int,Int](f => a => if(a<=0) 1 else f(a-1) * a)
fact: (Int) => Int = <function1>

scala> fact(12)
res0: Int = 479001600

注意它不能处理大数。在尾递归优化中要小心。


11
原文意思是“Amazing”是指让人感觉像迷失在迷宫中一样,需要翻译为:“Amazing”原本的意思是让我感到好像迷失在迷宫中。 "fix"是一个函数,它接受一个函数作为输入,该函数本身接受一个参数并返回另一个接受单个参数的函数,然后它... 现在我需要某人给我一个更好的解释... - Michael Lorton
但是这并不允许尾调用优化,我说的对吗? - fresskoma
8
@Malvolio: fix 是一个函数,它以另一个函数 f 为其参数,并返回该函数的 不动点(fixed point),即一个值 x,使得 f(x) == x。计算其他函数不动点的函数称为 不动点组合子。Y 组合子只是不动点组合子的一个例子。不动点组合子提供了一种在 lambda 演算等不允许自我引用定义的语言中描述递归的方式。 - Tom Crockett

27

如果你不想接触"Amazing mathematics",你可以转而使用Scala的对象方面。

val fact = new Function1[Int,Int]{
    def apply(x:Int):Int = if(x==1) x else x * apply(x-1)
}

15

为了让它看起来更加极客,你也可以使用这段代码样式:

val fact = new ((Int) => Int){
  def apply(x:Int):Int = if(x==1) x else x * apply(x-1)
}

这个看起来比简单的 new Function[Int, Int] {...} 好多了。谢谢! - Happy Torturer

6

在这个帖子中,除了已经有很多好的回答之外,Scala无法提供尾递归优化的不动点组合器一直困扰着我,所以我决定编写一个宏来将类Y组合器的调用转换为普通的、惯用的递归调用(当然要进行尾递归优化)。思路是像这样进行调用:

fix[Int,Int]((next) => (y) => ...body...)

可以轻松转化为
({(input) =>
  object next {
    def apply(y:Int):Int = ...body...
  }
  next(input)
})

我已经发布了针对Scala 2.11的宏实现(稍微调整一下应该也适用于2.10),位于此代码片段中。
使用此宏,我们可以在匿名方式下执行普通递归任务,而不必担心栈溢出问题,例如:
import asia.blip.ymacro.YMacro._
(y[BigInt,BigInt]((xx) => (y) => if(y==1) 1 else y * xx(y-1)))(2000)

提供

res0: BigInt = 33162750924506332411753933805763240382811...

我想知道你是否可以添加tailrec注释,以便它可以进行尾递归优化。 - Josh Cason
@JoshCason认为Scala足够聪明,可以推断出我所给出的尾递归示例。老实说,对于更复杂的用例,我不太确定。 - In-Ho Yi

1

Scala中的递归调用。 让我以计算N个数字之和的例子来说明递归

var sumIt:(Int => Int) = (x: Int) => {if(x<=1) 1 else sumIt(x-1)+x}


 scala> sumIt(10) 
val res141: Int = 55

您可以看到sumIt函数具有Int类型的输入和返回值。sumIt lambda函数以整数为参数,并在其上进行递归调用。

我只是用这个例子来方便理解递归调用。您可以直接使用公式来实现此逻辑,例如...

sumValue = (N*(N+1)) /2

0

在 @in-ho-yi 的 答案 上补充一点。为了简单起见,您可以直接在匿名函数内定义一个本地函数并将其用于递归。此外,在本地函数之前使用 @tailrec,您可以确保它将应用尾递归,而这是使用 Y 组合器无法做到的。

这比 Y 组合器更好,因为它只向堆栈添加 2 个框架,并且每次调用函数时只添加 1 个框架(如果它不是尾递归,则总共将使用 3 个框架,最多),而不是每次调用时添加 6 个框架,Y 组合器使用。

import scala.annotation.tailrec

val fact = {(input: BigInt)=>
  @tailrec
  def f(x:BigInt, acc: BigInt = 1):BigInt = {
    if(x<=1) acc
    else f(x-1, x*acc)
  }
  f(input)
}

print(fact(1024))

-1
一个非常简单的方法:
val fact = { (x: Int) =>
  def f(x: Int): Int = if (x == 0) 1 else x * f(x-1)
  f(x)
}

// Use as anonymous function below
(1 to 5).map { (x: Int) =>
  def f(x: Int): Int = if (x == 0) 1 else x * f(x-1)
  f(x)
}

// res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 6, 24, 120)

递归函数是f,它不是匿名的。它被定义在一个匿名函数内部并不改变这一事实。 - nafg
踩票是不必要的,因为该解决方案实现了问题所期望的目的。此外,在有两个其他解决方案使用def apply而不是def f与此非常相似的情况下,你却单独挑出了这个解决方案。 - pvillela
你的解决方案现在这样比如果将 def f 上移一行更正确吗?因为如果不是,那就证明了我的观点;如果是,请解释一下它们为什么不完全相同,除了 f 的作用域。 - nafg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接