web-dev-qa-db-ja.com

valまたはdefをScala traits?

私は effective scala slides を行っていましたが、スライド10では、抽象メンバーのvaltraitを使用せず、代わりにdefを使用するように言及しています。 valで抽象traitを使用することがアンチパターンである理由を詳細に説明します。

83
Mansur Ashraf

defは、defvallazy val、またはobjectのいずれかで実装できます。したがって、これは、メンバーを定義する最も抽象的な形式です。トレイトは通常抽象的なインターフェースであるため、valが欲しいということはhowと言っていることです。 valを要求する場合、実装クラスはdefを使用できません。

valは、安定した識別子が必要な場合にのみ必要です。パス依存型の場合。それはあなたが通常必要としないものです。


比較:

trait Foo { def bar: Int }

object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok

class F2(val bar: Int) extends Foo // ok

object F3 extends Foo {
  lazy val bar = { // ok
    Thread.sleep(5000)  // really heavy number crunching
    42
  }
}

あなたが持っていた場合

trait Foo { val bar: Int }

F1またはF3を定義することはできません。


わかりました。混乱させて@ om-nom-nomと答えると、抽象valsを使用すると初期化の問題が発生する可能性があります。

trait Foo { 
  val bar: Int 
  val schoko = bar + bar
}

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

これはい問題であり、個人的な意見では将来的にはなくなるはずですScala=コンパイラで修正することでバージョンが、はい、現在、これは抽象vals。

編集(2016年1月):lazy val実装で抽象val宣言をオーバーライドできるため、初期化の失敗も防止できます。

119
0__

Val宣言には初期化の順序が不明確で直感的でないため、特性にvalを使用しないことをお勧めします。すでに機能している階層に特性を追加すると、以前に機能していたすべてのものが壊れます。私のトピックを参照してください: 非最終クラスでプレーンvalを使用する理由

このval宣言の使用に関するすべてのことを念頭に置いて、最終的にエラーに至るようにしてください。


より複雑な例で更新する

ただし、valの使用を避けられない場合があります。 @ 0__が時々言及したように、安定した識別子が必要であり、defはそうではありません。

彼が話していることを示すための例を提供します。

trait Holder {
  type Inner
  val init : Inner
}
class Access(val holder : Holder) {
  val access : holder.Inner =
    holder.init
}
trait Access2 {
  def holder : Holder
  def access : holder.Inner =
    holder.init
}

このコードはエラーを生成します:

 StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
    def access : holder.Inner =

少し時間をかけて、コンパイラーが文句を言う理由があることを理解すると思います。の中に Access2.access何らかの方法で戻り値の型を導出できなかった場合。 def holderは、広範な方法で実装できることを意味します。呼び出しごとに異なるホルダーを返すことができ、そのホルダーには異なるInnerタイプが組み込まれます。ただし、Java仮想マシンは同じタイプが返されることを期待しています。

7
ayvango