在Scala中将功能重复N次

时间:2018-07-11 21:36:40

标签: scala

我想创建一个函数,该函数将返回一个是参数x的f的n倍的函数的n倍函数,即f(f(f ... f(x)...))。 这是我的代码:

def repeated(f: Int => Int, n: Int) = {
   var tek: Int => Int = f

   for (i <- 0 to n) {
     tek = x => f(tek(x))
   }
   tek
}

我知道这不是在Scala中执行此操作的正确方法,我只想了解幕后发生的事情。

repeated(x => x + 1, 5)(1)那样调用它会导致堆栈溢出。
我在调试器中注意到的是,重复完成后将执行for循环内的行。似乎是惰性启动,也许for循环的主体是按名称传递的lambda?

2 个答案:

答案 0 :(得分:4)

您的x => f(tek(x))正在关闭变量 tek。一旦内部for循环至少运行一次,您的tek就成为自引用的,因为tek = x => f(tek(x))会自行调用,这会导致无限递归和StackOverflowError

如果您想使用for循环来执行此操作,则可以引入局部不可变的辅助变量来中断递归:

def repeated(f: Int => Int, n: Int) = {
   var tek: Int => Int = identity

   for (i <- 1 to n) {
     val g = tek
     tek = x => f(g(x))
   }
   tek
}

请注意,您的代码中至少有两个f应用程序过多:

  1. 您并非以n = 0的身份开始
  2. 您从0迭代到n,即(n + 1)次。

一个更简单的解决方案是:

def repeated[A](f: A => A, n: Int): A => A = { (a0: A) =>

  var res: A = a0
  for (i <- 1 to n) {
    res = f(res)
  }
  res
}

答案 1 :(得分:4)

在纯FP中:

def repeated[A](f: A => A, n: Int): A => A =
  (0 until n).foldLeft(identity[A] _)((ff, _) => ff.andThen(f))

(如果n=0-变成identity也有效)

或者,如果您不喜欢遍历Range(我认为它的性能不会比其他方法低很多),请手动进行尾递归:

def repeated[A](f: A => A, n: Int): A => A = {
  @tailrec def aux(acc: A => A, n: Int): A => A = {
    if(n > 0) aux(acc.andThen(f), n - 1)
    else acc
  }

  aux(identity, n)
}

编辑:还有@@ Karl Bielefeldt提到的Stream版本。应该和性能差不多,但是最好的选择方法是用例进行基准测试:

def repeated[A](f: A => A, n: Int): A => A =
  Stream.iterate(identity[A] _)(_.andThen(f)).apply(n)

编辑2:如果您有猫:

def repeated[A](f: A => A, n: Int): A => A =
  MonoidK[Endo].algebra[A].combineN(f, n)