Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

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

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

Ответ 2

Я предпочитаю не использовать 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 ожидает, что тот же тип будет возвращен.

Ответ 3

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

trait Entity { def id:Int}

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

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

error: value id_= is not a member of Entity