web-dev-qa-db-fra.com

Quand utiliser val ou def dans les traits Scala?

Je parcourais les effective scala slides et il mentionne sur la diapositive 10 de ne jamais utiliser val dans un trait pour les membres abstraits et utilisez def à la place. La diapositive ne mentionne pas en détail pourquoi l'utilisation de l'abrégé val dans un trait est un anti-modèle. J'apprécierais que quelqu'un puisse expliquer les meilleures pratiques autour de l'utilisation de val vs def dans un trait pour les méthodes abstraites

83
Mansur Ashraf

Un def peut être implémenté par un def, un val, un lazy val ou object. C'est donc la forme la plus abstraite de définition d'un membre. Puisque les traits sont généralement des interfaces abstraites, dire que vous voulez un val dit comment l'implémentation devrait faire. Si vous demandez un val, une classe d'implémentation ne peut pas utiliser un def.

Un val n'est nécessaire que si vous avez besoin d'un identifiant stable, par exemple pour un type dépendant du chemin. C'est quelque chose dont vous n'avez généralement pas besoin.


Comparer:

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
  }
}

Si tu avais

trait Foo { val bar: Int }

vous ne pourriez pas définir F1 ou F3.


Ok, et pour vous embrouiller et répondre @ om-nom-nom - l'utilisation de vals abstraits peut causer des problèmes d'initialisation:

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

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

C'est un vilain problème qui, à mon avis, devrait disparaître à l'avenir Scala versions en le corrigeant dans le compilateur, mais oui, actuellement, c'est aussi une raison pour laquelle il ne faut pas utiliser l'abstrait vals.

Edit (Jan 2016): Vous êtes autorisé à remplacer une déclaration abstraite val par une lazy val l'implémentation, ce qui empêcherait également l'échec de l'initialisation.

119
0__

Je préfère ne pas utiliser val dans les traits car la déclaration val a un ordre d'initialisation peu clair et non intuitif. Vous pouvez ajouter un trait à une hiérarchie qui fonctionne déjà et cela casserait toutes les choses qui fonctionnaient auparavant, voir mon sujet: pourquoi utiliser plain val dans les classes non finales

Vous devez garder à l'esprit tout ce qui concerne l'utilisation de ces déclarations de valeur qui vous conduiront éventuellement à une erreur.


Mettre à jour avec un exemple plus compliqué

Mais il y a des moments où vous ne pouvez pas éviter d'utiliser val. Comme @ 0__ l'avait mentionné parfois, vous avez besoin d'un identifiant stable et def n'en fait pas partie.

Je donnerais un exemple pour montrer de quoi il parlait:

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
}

Ce code produit l'erreur:

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

Si vous prenez une minute pour penser, vous comprendrez que le compilateur a une raison de se plaindre. Dans le Access2.access cas où il ne pouvait en aucun cas dériver le type de retour. def holder signifie qu'il pourrait être mis en œuvre de manière large. Il pourrait renvoyer différents titulaires pour chaque appel et ces titulaires incorporeraient différents types Inner. Mais Java attend que le même type soit renvoyé.

7
ayvango