8

私はScalaで暗黙のパラメーターがどのように機能するかを把握しようとしています。私が知る限り、暗黙のパラメータ解決は次のようになります。

  1. オブジェクトをメソッドに明示的に渡します。
  2. スコープで定義された暗黙の定義。
  3. 暗黙のパラメータとして使用されるクラスのコンパニオンオブジェクト

しかし、怠惰なvalsと組み合わせてこれをいじり始めたとき、私は少し驚きました。怠惰なvalsは最後の解決ルールのみを使用するようです。説明のためのサンプルコードを次に示します。

class Bar(val name:String)
object Bar { implicit def bar = new Bar("some default bar") }

class Foo {
  lazy val list = initialize
  def initialize(implicit f:Bar) = {
    println("initialize called with Bar: '" + f.name + "' ...")
    List[Int]()
  }
}

trait NonDefaultBar extends Foo {
  implicit def f = new Bar("mixed in implicit bar")
  def mixedInInit = initialize
  lazy val mixedInList = list
}

object Test {
    def test = {
      println("Case 1: with implicitp parameter from companion object")
      val foo1 = new Foo
      foo1.list
      foo1.initialize

      println("Case 2: with mixedin implicit parameter overriding the default one...")
      val foo2 = new Foo with NonDefaultBar 
      foo2.mixedInList

      val foo3 = new Foo with NonDefaultBar 
      foo3.mixedInInit

      println("Case 3: with local implicit parameter overriding the default one...")
      implicit def nonDefaultBar = new Bar("locally scoped implicit bar")
      val foo4 = new Foo 
      foo4.list
      foo4.initialize
    }
}

呼び出すTest.testと、次の出力が得られます。

Case 1: with implicitp parameter from companion object 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'some default bar' ... 
Case 2: with mixedin implicit parameter overriding the default one... 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'mixed in implicit bar'... 
Case 3: with local implicit parameter overriding the default one... 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'locally scoped implicit bar' ...

ケース2でmixedInListを呼び出すときに、コンパイラが暗黙のバーが混在していることをキャッチしないのはなぜですか。ケース3では、リストにアクセスするときにローカルで定義された暗黙のバーも見逃されます。

コンパニオンオブジェクトで定義された暗黙を使用しない遅延値で暗黙のパラメーターを使用する方法はありますか?

4

2 に答える 2

4

これはimplicit Bar、コンパイラがFooクラスをコンパイルするときに他に何もないためです。Javaで逆コンパイルされたコードは次のようになります。

public class Foo
  implements ScalaObject
{
  private List<Object> list;
  public volatile int bitmap$0;

  public List<Object> list()
  {
    if (
      (this.bitmap$0 & 0x1) == 0);
    synchronized (this)
    {
      if (
        (this.bitmap$0 & 0x1) == 0) {
        this.list = initialize(Bar..MODULE$.bar()); this.bitmap$0 |= 1; } return this.list;
    }
  }
  public List<Object> initialize(Bar f) { Predef..MODULE$.println(new StringBuilder().append("initialize called with Bar: '").append(f.name()).append("' ...").toString());
    return Nil..MODULE$;
  }
}

これlazy valは、変数がすでに設定されているかどうかをチェックして返すか、設定してから返すメソッドにすぎません。したがって、ミックスインはまったく考慮されません。それが必要な場合は、自分で初期化を行う必要があります。

于 2012-04-15T14:07:44.400 に答える
2

scalaは暗黙的なパラメーターを持つレイジー値をサポートしていませんが、オプションを使用して自分で定義することができます。したがって、解決策は次のものを置き換えることです。

lazy val list = initialize

private var _list: Option[List[Int]] = None
def list(implicit f: Bar) = 
  _list.getOrElse{
    _list = Some(initialize)
    _list.get
  }

次に実行Test.testすると、期待される結果が表示されます。

Case 1: with implicitp parameter from companion object
initialize called with Bar: 'some default bar' ...
initialize called with Bar: 'some default bar' ...
Case 2: with mixedin implicit parameter overriding the default one...
initialize called with Bar: 'mixed in implicit bar' ...
initialize called with Bar: 'mixed in implicit bar' ...
Case 3: with local implicit parameter overriding the default one...
initialize called with Bar: 'locally scoped implicit bar' ...
initialize called with Bar: 'locally scoped implicit bar' ...

変更可能なオプションがある場合は、怠惰なvalを2行だけに置き換えて、同じ結果を得ることができることに注意してください。

private val _list: MutableOpt[List[Int]] = MutableOpt.from(None)
def list(implicit f: Bar) = _list.getOrSet(initialize)

個人的には、いつかScalaがこれを1行で書けるようになることを願っています。

lazy val list(implicit f: Bar) = initialize

これは完全に意味があります。変数リストの値にアクセスするときはいつでも、スコープ内にBarが必要ですが、最初の計算のみが重要になります。

于 2017-03-22T15:12:28.260 に答える