Kapan menggunakan val atau def pada ciri-ciri Scala?

90

Saya melihat slide skala yang efektif dan itu menyebutkan pada slide 10 untuk tidak pernah digunakan valdalam traituntuk anggota abstrak dan defsebagai gantinya. Slide tidak menyebutkan secara rinci mengapa menggunakan abstrak valdalam traitbentuk anti-pola. Saya akan sangat menghargai jika seseorang dapat menjelaskan praktik terbaik seputar penggunaan val vs def dalam suatu sifat untuk metode abstrak

Mansur Ashraf
sumber

Jawaban:

130

A defdapat diimplementasikan oleh a def, a val, a lazy valatau an object. Jadi ini adalah bentuk paling abstrak untuk mendefinisikan anggota. Karena sifat biasanya antarmuka abstrak, mengatakan Anda menginginkan a valberarti mengatakan bagaimana implementasi harus dilakukan. Jika Anda meminta a val, kelas pelaksana tidak dapat menggunakan def.

A valdiperlukan hanya jika Anda membutuhkan pengenal yang stabil, misalnya untuk tipe yang bergantung pada jalur. Itu adalah sesuatu yang biasanya tidak Anda butuhkan.


Membandingkan:

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

Jika Anda punya

trait Foo { val bar: Int }

Anda tidak akan bisa mendefinisikan F1atau F3.


Oke, dan untuk membingungkan Anda dan jawab @ om-nom-nom — menggunakan abstrak valdapat menyebabkan masalah inisialisasi:

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

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

Ini adalah masalah buruk yang menurut pendapat pribadi saya harus hilang di versi Scala mendatang dengan memperbaikinya di kompiler, tapi ya, saat ini ini juga merupakan alasan mengapa seseorang tidak boleh menggunakan abstrak val.

Sunting (Jan 2016): Anda diizinkan untuk mengganti valdeklarasi abstrak dengan lazy valimplementasi, sehingga akan mencegah kegagalan inisialisasi.

0__
sumber
8
kata-kata tentang urutan inisialisasi yang rumit dan nulls mengejutkan?
om-nom-nom
Ya ... Saya bahkan tidak akan pergi ke sana. Benar, ini juga merupakan argumen yang menentang val, tetapi menurut saya motivasi dasarnya adalah untuk menyembunyikan implementasi.
0__
2
Ini mungkin telah berubah dalam versi Scala terbaru (2.11.4 pada komentar ini), tetapi Anda dapat menimpanya valdengan a lazy val. Pernyataan Anda bahwa Anda tidak akan dapat membuat F3jika baritu valtidak benar. Yang mengatakan, anggota abstrak dalam sifat harus selalu def's
mplis
Contoh Foo / Fail bekerja seperti yang diharapkan jika Anda mengganti val schoko = bar + bardengan lazy val schoko = bar + bar. Itulah salah satu cara untuk memiliki kendali atas urutan inisialisasi. Selain itu, menggunakan lazy valalih-alih defdi kelas turunan menghindari penghitungan ulang.
Adrian
2
Jika diubah val bar: Intke def bar: Int Fail.schokomasih nol.
Jasper-M
8

Saya lebih suka tidak menggunakan valsifat karena deklarasi val memiliki urutan inisialisasi yang tidak jelas dan tidak intuitif. Anda dapat menambahkan sifat ke hierarki yang sudah berfungsi dan itu akan merusak semua hal yang berfungsi sebelumnya, lihat topik saya: mengapa menggunakan val biasa di kelas non-final

Anda harus mengingat semua hal tentang menggunakan deklarasi val ini yang pada akhirnya mengarahkan Anda ke kesalahan.


Perbarui dengan contoh yang lebih rumit

Tetapi ada kalanya Anda tidak bisa menghindari penggunaan val. Seperti yang telah disebutkan @ 0__ terkadang Anda memerlukan pengenal yang stabil dan defbukan pengenal .

Saya akan memberikan contoh untuk menunjukkan apa yang dia bicarakan:

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
}

Kode ini menghasilkan kesalahan:

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

Jika Anda meluangkan waktu sejenak untuk berpikir Anda akan mengerti bahwa kompilator memiliki alasan untuk mengeluh. Dalam Access2.accesskasus ini tidak bisa mendapatkan tipe pengembalian dengan cara apapun. def holderartinya bisa diimplementasikan secara luas. Itu bisa mengembalikan pemegang yang berbeda untuk setiap panggilan dan pemegang itu akan memasukkan Innerjenis yang berbeda . Tetapi mesin virtual Java mengharapkan jenis yang sama dikembalikan.

ayvango
sumber
3
Urutan inisialisasi seharusnya tidak menjadi masalah, tetapi sebaliknya kami mendapatkan NPE yang mengejutkan selama runtime, berhadapan dengan anti-pola.
Jonathan Neufeld
scala memiliki sintaks deklaratif yang menyembunyikan sifat imperatif. Terkadang
ketidaksabaran
-4

Selalu menggunakan def sepertinya agak canggung karena sesuatu seperti ini tidak akan berfungsi:

trait Entity { def id:Int}

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

Anda akan mendapatkan kesalahan berikut:

error: value id_= is not a member of Entity
Dimitry
sumber
2
Tidak relevan. Anda juga memiliki kesalahan jika Anda menggunakan val daripada def (kesalahan: penugasan ulang ke val), dan itu sangat logis.
volia17
Tidak jika Anda menggunakan var. Intinya adalah, jika itu adalah bidang, mereka harus ditetapkan seperti itu. Saya hanya berpikir memiliki segalanya karena defrabun dekat.
Dimitry
@Dimitry, tentu, menggunakan varmari kita hancurkan enkapsulasi. Tetapi menggunakan def(atau a val) lebih disukai daripada variabel global. Saya pikir apa yang Anda cari adalah sesuatu seperti case class ConcreteEntity(override val id: Int) extends Entitysehingga Anda dapat membuatnya dari def create(e: Entity) = ConcreteEntity(1)Ini lebih aman daripada memecahkan enkapsulasi dan mengizinkan kelas apa pun untuk mengubah Entitas.
Jono