我想创建一个函数,该函数将返回一个是参数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?
答案 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
应用程序过多:
n = 0
的身份开始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)