Когда использовать val или def в чертах Scala?


Я шел через эффективные слайды scala и он упоминает на слайде 10, чтобы никогда не использовать val на trait для абстрактных членов и использовать def вместо. Слайд не упоминает подробно, почему с помощью абстрактного val на trait - это анти-паттерн. Я был бы признателен, если кто-то может объяснить лучшую практику использования val vs def в черте для абстрактных методов

3 71

3 ответа:

A def может быть реализован либо def, a val, a lazy val или object. Так что это самая абстрактная форма определения члена. Поскольку черты обычно являются абстрактными интерфейсами, говоря, что вы хотите val говорит как реализация должна сделать. Если вы просите val, реализующий класс не может использовать def.

A 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-using abstract vals может вызвать проблемы с инициализацией:

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

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

это уродливая проблема, которая, по моему личному мнению, должна уйти в будущих версиях Scala, исправив ее в компиляторе, Но да, в настоящее время это также причина, по которой не следует использовать abstract val s.

Edit (Jan 2016): вы можете переопределить абстрактный val декларации lazy val реализация, так что бы также предотвратить сбой инициализации.

Я предпочитаю не использовать 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 case он никак не мог получить тип возвращаемого значения. def holder означает, что он может быть реализован в широком смысле. Он может возвращать разные держатели для каждого вызова, и что держатели будут включать разные Inner типы. Но виртуальная машина Java ожидает, что будет возвращен тот же тип.

всегда использовать def кажется немного неудобным, так как что-то вроде этого не будет работать:

trait Entity { def id:Int}

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

вы получите следующее сообщение об ошибке:

error: value id_= is not a member of Entity