3

関数呼び出しがどの程度深くネストされているかについての基本的な知識が必要です。次のことを考慮してください。

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

アイデアは、decorate関数がそのネストの深さを知っているということです。アイデア?

更新:ニキータが考えていたように、私の例は私が本当に求めているものを表していない。目標は、同じ入れ子関数への一連の呼び出しを通じて何らかの状態を渡すことができるほど文字列を生成しないことです。RégisJean-Gillesは私を正しい方向に向けていると思います。

4

3 に答える 3

4

動的スコープパターンを使用できます。もっと端的に言えば、これはスレッドローカル変数(scalaDynamicVariableはそのためだけに行われます)を使用して現在のネストレベルを保存することを意味します。このパターンの特定の例については、この他の質問に対する私の回答を参照してください。引数として関数リテラル(暗黙のパラメーターを使用)を受け取る関数を定義するにはどうすればよいですか?

ただし、これは、非常に特定の方法のネストレベルを知りたい場合にのみ適しています。どのメソッドでも機能する一般的なメカニズムが必要な場合、これは機能しません(メソッドごとに個別の変数が必要になるため)。この場合、私が考えることができる唯一の選択肢はスタックを検査することですが、それはあまり信頼できないだけでなく、非常に遅いです。

更新:実際に、動的スコープパターンを一般的な方法で適用する方法があります(可能な方法であれば)。重要なのは、メソッドごとに一意のIDを暗黙的に取得できるようにすることです。そこから、このIDをキーとして使用してaDynamicVariableをメソッドに関連付けるだけです。

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を取得するために、実際に渡されたクロージャーのIDをwithFunctionNesting取得します(現在のネストを取得する必要があるメソッドで呼び出す必要があります)。そして、それは私が実装に依存する側で誤りを犯すところです:idは関数インスタンスの単なるクラスです。これは現在のところ期待どおりに機能します(すべての単項関数リテラルは実装されている1つのクラスとして実装されているFunction1ため、クラスは一意のIDとして機能します)が、実際には、将来のバージョンのscalaでは壊れてしまう可能性があります(可能性は低いですが)。 。したがって、自己責任で使用してください。

最後に、ニキータ・ボルコフのより機能的な提案が全体としてより良い解決策ではないかどうかを最初に真剣に評価することをお勧めします。

于 2012-11-07T20:01:49.683 に答える
0

関数から数値を返し、スタックに戻る途中のレベル数を数えることができます。しかし、出力例を示したように、途中で数える簡単な方法はありません。

于 2012-11-07T19:58:46.473 に答える
0

あなたの質問は「関数型プログラミング」でタグ付けされているので、以下は関数型ソリューションです。プログラムロジックが完全に変更されていることは確かですが、サンプルコードは必須でした。

関数型プログラミングの基本原則は、状態がないということです。すべての頭痛の種(マルチスレッドの問題など)を伴う命令型プログラミングで共有状態として使用されていたもの-関数型プログラミングで引数として不変データを渡すことによってすべてが達成されます。

したがって、渡したい「状態」データが現在のサイクル数であると仮定すると、再帰を使用して関数を実装する方法は次のとおりです。

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))

または、ワーカー関数を非再帰的にして「折りたたむ」こともできます。

def decorated ( a : String, times : Int )
  = "I am decorated " + times + "x\n" + a

println( (1 to 3).foldLeft("foo")(decorated) )

上記の両方のコードは、次の出力を生成します。

I am decorated 3x
I am decorated 2x
I am decorated 1x
foo
于 2012-11-07T20:40:56.287 に答える