如何在Scala中跟踪嵌套函数

如何在Scala中跟踪嵌套函数,scala,functional-programming,Scala,Functional Programming,我想了解函数调用嵌套的深度的一些基本知识。考虑以下事项: scala> def decorate(f: => Unit) : Unit = { println("I am decorated") ; f } decorate: (f: => Unit)Unit scala> decorate { println("foo") } I am decorated foo scala> decorate { decorate { println("foo") } }

我想了解函数调用嵌套的深度的一些基本知识。考虑以下事项:

scala> def decorate(f: => Unit) : Unit = { println("I am decorated") ; f }
decorate: (f: => Unit)Unit

scala>  decorate { println("foo") }
I am decorated
foo

scala> decorate { decorate { println("foo") } }
I am decorated
I am decorated
foo
I am decorated 2x
I am decorated 1x
foo
对于最后一个电话,我希望能够获得以下信息:

scala> def decorate(f: => Unit) : Unit = { println("I am decorated") ; f }
decorate: (f: => Unit)Unit

scala>  decorate { println("foo") }
I am decorated
foo

scala> decorate { decorate { println("foo") } }
I am decorated
I am decorated
foo
I am decorated 2x
I am decorated 1x
foo
其思想是
decoration
函数知道其嵌套的深度。想法


更新:正如尼基塔所想,我的例子并不代表我真正想要的。我们的目标不是生成字符串,而是能够通过对同一嵌套函数的一系列调用传递某些状态。我认为Régis Jean Gilles为我指明了正确的方向。

您可以从函数中返回一个数字,并在返回堆栈的过程中计算您所处的级别。但是,没有一种简单的方法可以像您给出的示例输出那样计算下降的幅度。

您可以使用动态范围模式。更平淡地说,这意味着使用线程局部变量(scala的
DynamicVariable
就是为此而做的)来存储当前嵌套级别。关于这种模式的具体示例,请参见我对另一个问题的回答:

但是,只有当您想知道一个非常特定的方法的嵌套级别时,这才适用。如果您想要一个适用于任何方法的通用mecanism,那么这将不起作用(因为每个方法都需要一个不同的变量)。在这种情况下,我能想到的唯一替代方法是检查堆栈,但它不仅不太可靠,而且速度非常慢

更新:实际上,有一种方法可以以通用方式应用动态范围模式(适用于任何可能的方法)。重要的部分是能够隐式地为每个方法获取唯一的id。从这里开始,只需将此id用作将
动态变量
关联到方法的键:

import scala.util.DynamicVariable
object FunctionNestingHelper {  
  private type FunctionId = Class[_]
  private def getFunctionId( f: Function1[_,_] ): FunctionId = {
    f.getClass // That's it! Beware, implementation dependant.
  }
  private val currentNestings = new DynamicVariable( Map.empty[FunctionId, Int] )
  def withFunctionNesting[T]( body: Int => T ): T = {
    val id = getFunctionId( body )
    val oldNestings = currentNestings.value 
    val oldNesting = oldNestings.getOrElse( id, 0 )
    val newNesting = oldNesting + 1
    currentNestings.withValue( oldNestings + ( id -> newNesting) ) {
      body( newNesting )
    }    
  }
}
用法:

import FunctionNestingHelper._
def decorate(f: => Unit)  = withFunctionNesting { nesting: Int =>
  println("I am decorated " + nesting + "x") ; f 
}
为了获取方法的唯一id,我实际上获取了传递给
with functionnesting
的闭包的id(您必须在需要检索当前嵌套的方法中调用该闭包)。这就是我在依赖于实现的方面出错的地方:id只是函数实例的类。到目前为止,这确实可以像预期的那样工作(因为每个一元函数文本都被实现为一个实现
Function1
的类,因此该类充当一个唯一的id),但现实情况是,它可能会在未来的scala版本中崩溃(尽管不太可能)。因此,使用它的风险自负


最后,我建议您首先认真评估Nikita Volkov提出的功能性更强的建议是否不是一个更好的整体解决方案。

因为您的问题标有“功能性编程”,以下是功能性解决方案。当然程序逻辑完全改变了,但是您的示例代码是必需的

函数式编程的基本原理是没有状态。在命令式编程中,您习惯于将所有头痛的问题(多线程问题等)作为共享状态—这一切都是通过在函数式编程中将不可变数据作为参数传递来实现的

因此,假设您要传递的“状态”数据是当前循环数,下面是如何使用递归实现函数:

def decorated ( a : String, cycle : Int ) : String
  = if( cycle <= 0 ) a
    else "I am decorated " + cycle + "x\n" + decorated(a, cycle - 1)

println(decorated("foo", 3))
上述两个代码将产生以下输出:

I am decorated 3x
I am decorated 2x
I am decorated 1x
foo

这个问题看起来非常适合递归或
展开
-
折叠
。但您当前的示例是必需的,为函数算法重写它将从根本上改变它的结构和逻辑,因此我担心它会偏离您实际需要的内容。如果你提供了一个更接近你真正问题的代码,或者重写了当前的代码使其功能化,这会有很大帮助;好主意。也似乎是一个非常好的单子集合候选,比如Scala的选项类型。在这种情况下,monad会将函数
a=>B
转换为函数
a=>(B,Int)
,其中整数是调用堆栈中的当前深度。您可以将其设置为实现map、flatmap和filter,然后你就可以用它来理解了。你甚至可以指定你的一元类型的两个实现——一个计算调用,一个不计算调用——这样你就可以只跟踪你感兴趣的特定函数的调用次数。请参见更新