Scalaトレイトでvalまたはdefを使用するのはいつですか?


90

私は効果的なスカラスライド調べていましたが、スライド10には、抽象メンバーvalには絶対にtrait使用せdefず、代わりに使用するように記載されています。スライドではval、atraitでabstractを使用することがアンチパターンである理由については詳しく説明していません。誰かが抽象メソッドの特性でvalvsdefを使用することに関するベストプラクティスを説明できれば幸いです

回答:


130

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

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


比較:

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月):抽象val宣言をlazy val実装でオーバーライドできるため、初期化の失敗も防止できます。


8
トリッキーな初期化順序と驚くべきnullについての言葉?
om-nom-nom 2013年

ええ...私もそこに行きません。確かにこれらもvalに対する議論ですが、基本的な動機は実装を隠すことだけであるべきだと思います。
0__ 2013年

2
これは最近のScalaバージョン(このコメントの時点では2.11.4)で変更されている可能性がありますが、valをでオーバーライドできますlazy val。だっF3たら作成できないというあなたの主張は正しくありません。とは言うbarvaldef
ものの

に置き換えるval schoko = bar + barと、Foo / Failの例は期待どおりに機能しますlazy val schoko = bar + bar。これは、初期化の順序をある程度制御する1つの方法です。また、派生クラスでlazy val代わりにを使用するとdef、再計算が回避されます。
エイドリアン

2
に変更val bar: Intdef bar: Int Fail.schokoてもまだゼロです。
jasper-M

8

valval宣言には不明確で直感的でない初期化の順序があるため、トレイトで使用することは好みません。すでに機能している階層にトレイトを追加すると、以前は機能していたすべてのものが壊れてしまいます。私のトピックを参照してください:非最終クラスでプレーン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仮想マシンは、同じタイプが返されることを想定しています。


3
初期化の順序は重要ではありませんが、代わりに、アンチパターンに対して、実行時に驚くべきNPEが発生します。
ジョナサンノイフェルド2014

scalaには、命令型の性質を背後に隠す宣言型構文があります。時々、その必須性は直感に反して機能します
ayvango 2014

-4

このようなものは機能しないため、常にdefを使用するのは少し厄介なようです。

trait Entity { def id:Int}

object Table { 
  def create(e:Entity) = {e.id = 1 }  
}

次のエラーが発生します。

error: value id_= is not a member of Entity

2
関係ありません。defの代わりにvalを使用した場合にもエラーが発生します(エラー:valへの再割り当て)。これは完全に論理的です。
volia17 2015年

を使用する場合は違いますvar。重要なのは、それらがフィールドである場合、そのように指定する必要があるということです。私はただdef近視眼的なものとしてすべてを持っていると思います。
Dimitry 2015年

@Dimitry、確かに、varカプセル化を破りましょう。ただし、def(またはval)を使用することは、グローバル変数よりも優先されます。あなたが探しているのは、case class ConcreteEntity(override val id: Int) extends Entityそこから作成できるようなものだと思います。def create(e: Entity) = ConcreteEntity(1)これは、カプセル化を解除して任意のクラスにEntityの変更を許可するよりも安全です。
Jono 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.