21

この質問の長さはご容赦ください。

多くの場合、コードの1つのレイヤーでコンテキスト情報を作成し、その情報を他の場所で使用する必要があります。私は通常、暗黙のパラメータを使用していることに気付きます。

def foo(params)(implicit cx: MyContextType) = ...

implicit val context = makeContext()
foo(params)

これは機能しますが、暗黙のパラメーターを頻繁に渡す必要があり、介在する関数のレイアウト後にレイヤーのメソッドシグネチャを汚染します(それ自体は気にしない場合でも)。

def foo(params)(implicit cx: MyContextType) = ... bar() ...
def bar(params)(implicit cx: MyContextType) = ... qux() ...
def qux(params)(implicit cx: MyContextType) = ... ged() ...
def ged(params)(implicit cx: MyContextType) = ... mog() ...
def mog(params)(implicit cx: MyContextType) = cx.doStuff(params)

implicit val context = makeContext()
foo(params)

このアプローチは醜いと思いますが、1つの利点があります。それはタイプセーフです。mog正しいタイプのコンテキストオブジェクトを受け取るか、コンパイルされないかは確実にわかっています。

何らかの形の「依存性注入」を使用して関連するコンテキストを見つけることができれば、混乱が緩和されます。引用符は、これがScalaで見られる通常の依存性注入パターンとは異なることを示すためにあります。

開始点fooと終了点mogは、システムの非常に異なるレベルに存在する場合があります。たとえばfoo、ユーザーログインコントローラーであり、mogSQLアクセスを実行している可能性があります。一度に多くのユーザーがログインしている可能性がありますが、SQLレイヤーのインスタンスは1つだけです。mog異なるユーザーによって呼び出されるたびに、異なるコンテキストが必要になります。したがって、コンテキストを受信オブジェクトにベイクすることはできません。また、2つのレイヤーを何らかの方法でマージすることもできません(ケーキパターンなど)。また、GuiceやSpringのようなDI/IoCライブラリに依存したくありません。私はそれらが非常に重く、Scalaにはあまり適していないことを発見しました。

したがって、私が必要だと思うのはmog、実行時に正しいコンテキストオブジェクトを取得できるものThreadLocalで、スタックが含まれているようなものです。

def foo(params) = ...bar()...
def bar(params) = ...qux()...
def qux(params) = ...ged()...
def ged(params) = ...mog()...
def mog(params) = { val cx = retrieveContext(); cx.doStuff(params) }

val context = makeContext()
usingContext(context) { foo(params) }

しかし、非同期アクターがチェーンのどこかに関与するとすぐに、それは低下します。どのアクターライブラリを使用するかは関係ありません。コードが別のスレッドで実行されると、が失われThreadLocalます。

だから...私が見逃しているトリックはありますか?介在するメソッドシグネチャを汚染せず、コンテキストをレシーバーに静的に焼き付けず、タイプセーフな情報をScalaでコンテキスト的に渡す方法はありますか?

4

5 に答える 5

11

Scala標準ライブラリには、DynamicVariableと呼ばれる架空の「usingContext」のようなものが含まれています。この質問には、scala.util.DynamicVariableをいつ使用する必要があるかについての情報があります。。DynamicVariableは内部でThreadLocalを使用するため、ThreadLocalに関する問題の多くは残ります。

リーダーモナドは、環境http://debasishg.blogspot.com/2010/12/case-study-of-cleaner-composition-of.htmlを明示的に渡すための機能的な代替手段です。Readerモナドは、Scalazhttp : //code.google.com/p/scalaz/にあります。ただし、ReaderMonadは、型を変更する必要があるという点で署名を「汚染」します。一般に、モナディックプログラミングはコードに多くの再構築を引き起こす可能性があり、パフォーマンスやメモリが懸念される場合は、すべてのクロージャへの余分なオブジェクト割り当てが適切に行われない可能性があります。

これらの手法はどちらも、アクターメッセージ送信を介してコンテキストを自動的に共有しません。

于 2011-12-08T18:06:46.133 に答える
7

パーティーに少し遅れましたが、クラスコンストラクターに暗黙のパラメーターを使用することを検討しましたか?

class Foo(implicit biz:Biz) {
   def f() = biz.doStuff
}
class Biz {
   def doStuff = println("do stuff called")
}

呼び出しごとに新しいbizが必要なf()場合は、暗黙のパラメーターを新しいbizを返す関数にすることができます。

class Foo(implicit biz:() => Biz) {
   def f() = biz().doStuff
}

ここで、を作成するときにコンテキストを提供する必要がありますFoo。あなたはこのように行うことができます:

trait Context {
    private implicit def biz = () => new Biz
    implicit def foo = new Foo // The implicit parameter biz will be resolved to the biz method above
}

class UI extends Context {
    def render = foo.f()
}

暗黙のbizメソッドはに表示されないことに注意してくださいUI。したがって、基本的にこれらの詳細を隠します:)

依存性注入に暗黙のパラメーターを使用することについてのブログ投稿を書きました。これはここにあります(恥知らずな自己宣伝;))

于 2012-05-08T06:26:58.673 に答える
2

リフトからの依存性注入はあなたが望むことをするのだと思います。doWith()メソッドの使用の詳細については、wikiを参照してください。

リフトを実行していない場合でも、別のライブラリとして使用できることに注意してください。

于 2011-12-08T17:23:56.490 に答える
1

あなたはちょうど約1年前にこれを尋ねました、しかしここに別の可能性があります。1つのメソッドのみを呼び出す必要がある場合:

def fooWithContext(cx: MyContextType)(params){
    def bar(params) = ... qux() ...
    def qux(params) = ... ged() ...
    def ged(params) = ... mog() ...
    def mog(params) = cx.doStuff(params)
    ... bar() ...
}

fooWithContext(makeContext())(params)

すべてのメソッドを外部から表示する必要がある場合:

case class Contextual(cx: MyContextType){
    def foo(params) = ... bar() ...
    def bar(params) = ... qux() ...
    def qux(params) = ... ged() ...
    def ged(params) = ... mog() ...
    def mog(params) = cx.doStuff(params)
}

Contextual(makeContext()).foo(params)

これは基本的にケーキのパターンですが、すべてのものが1つのファイルに収まる場合は、すべての厄介なtraitものを1つのオブジェクトに結合する必要はなく、ネストするだけで済みます。このようにすることで、cx適切な字句スコープも作成されるため、先物やアクターなどを使用するときに、おかしな動作になってしまうことはありません。新しいAnyValを使用すれば、Contextualオブジェクトを割り当てるオーバーヘッドをなくすことができると思います。

traitsを使用してコンテンツを複数のファイルに分割する場合trait、すべてを保持しMyContextType、スコープ内に適切に配置するために必要なのは、ファイルごとに1つだけです。例があります。

// file1.scala
case class Contextual(cx: MyContextType) with Trait1 with Trait2{
    def foo(params) = ... bar() ...
    def bar(params) = ... qux() ...
}

// file2.scala
trait Trait1{ self: Contextual =>
    def qux(params) = ... ged() ...
    def ged(params) = ... mog() ...
}

// file3.scala
trait Trait2{ self: Contextual =>
    def mog(params) = cx.doStuff(params)
}

// file4.scala
Contextual(makeContext()).foo(params)

小さな例では少し厄介に見えますが、コードが大きくなりすぎて1つのファイルに収まらない場合にのみ、新しい特性に分割する必要があることを忘れないでください。その時点で、ファイルはかなり大きいので、200〜500行のファイルに2行余分に追加してもそれほど悪くはありません。

編集:

これは非同期のものでも機能します

case class Contextual(cx: MyContextType){
    def foo(params) = ... bar() ...
    def bar(params) = ... qux() ...
    def qux(params) = ... ged() ...
    def ged(params) = ... mog() ...
    def mog(params) = Future{ cx.doStuff(params) }
    def mog2(params) = (0 to 100).par.map(x => x * cx.getSomeValue )
    def mog3(params) = Props(new MyActor(cx.getSomeValue))
}

Contextual(makeContext()).foo(params)

ネスティングを使用して機能します。同様の機能をで動作させることができれば、私は感銘を受けるでしょうDynamicVariable

Future作成時に現在を格納する特別なサブクラスが必要であり、を実行する前にを抽出して適切に設定するために、またはメソッドにDynamicVariable.valueフックする必要ExecutionContextがあります。prepare()execute()valueDynamicVariableFuture

scala.collection.parallel.TaskSupport次に、並列コレクションを機能させるために、同様のことを行うための特別な操作が必要になります。そして、そのために似たようなことをするための特別akka.actor.Propsな。

非同期タスクを作成する新しいメカニズムがあるたびに、DynamicVariableベースの実装が壊れて、間違ったものをプルアップするという奇妙なバグが発生しますContextDynamicVariable追跡するために新しいものを追加するたびに、この新しいものを適切に設定/設定解除するために、すべての特別なエグゼキュータにパッチを適用する必要がありますDynamicVariable。ネストを使用すると、字句クロージャにこれらすべてを処理させることができます。

Futurescollections.parallelPropsは、「その間のレイヤーは私のコードではない」と見なされると思います)

于 2013-01-04T06:24:07.347 に答える
1

暗黙のアプローチと同様に、Scalaマクロを使用すると、コンストラクターを使用してオブジェクトの自動配線を行うことができます。私のMacWireプロジェクトを参照してください(そして自己宣伝を許してください)。

MacWireにもスコープがあります(かなりカスタマイズ可能で、ThreadLocal実装が提供されています)。ただし、ライブラリを使用してアクター呼び出し間でコンテキストを伝播できるとは思いません。識別子を持ち歩く必要があります。これは、たとえば、アクターメッセージを送信するためのラッパーを介して、またはより直接的にメッセージを使用して行うことができます。

次に、識別子がリクエスト/セッションごと/スコープが何であれ一意である限り、プロキシを介してマップで物事を検索するだけです(MacWireスコープのように、ここでの「識別子」は必要ありません。に保存されますThreadLocal)。

于 2013-04-30T13:30:22.583 に答える