Scala'da val-değişkene karşı var-immutable


100

Değişmez bir koleksiyonla var kullanmak yerine değişebilen bir koleksiyonla val'in ne zaman kullanılacağına ilişkin Scala'da herhangi bir kılavuz var mı? Yoksa gerçekten değişmez bir koleksiyonla vali mi hedeflemelisiniz?

Her iki koleksiyon türünün de olması bana çok fazla seçenek veriyor ve çoğu zaman bu seçimi nasıl yapacağımı bilmiyorum.


Yanıtlar:


105

Bu oldukça yaygın bir soru. Zor olan, kopyaları bulmaktır.

Referans şeffaflığı için çabalamalısınız . Ne demek olduğunu ben bir ifade "e" varsa, ben yapabilirim, yani val x = e, ve değiştirme eile x. Bu, değişebilirliği bozan özelliktir. Bir tasarım kararı vermeniz gerektiğinde, referans şeffaflığı en üst düzeye çıkarın.

Pratik bir mesele olarak, yöntem-yerel var, yöntemden kaçmadığı variçin var olan en güvenlisidir . Yöntem kısaysa, daha da iyi. Değilse, diğer yöntemleri çıkararak onu azaltmaya çalışın.

Öte yandan, değişken bir koleksiyon, olmasa bile kaçma potansiyeline sahiptir . Kodu değiştirirken, daha sonra başka yöntemlere geçirmek veya iade etmek isteyebilirsiniz. Bu, referans şeffaflığı bozan türden bir şey.

Bir nesnede (bir tarlada), hemen hemen aynı şey olur, ancak daha vahim sonuçları olur. Her iki durumda da nesnenin durumu olur ve bu nedenle, referans şeffaflığı bozulur. Ancak değiştirilebilir bir koleksiyona sahip olmak, nesnenin kendisi bile onu kimin değiştirdiğinin kontrolünü kaybedebileceği anlamına gelir.


39
Güzel, zihnimde yeni büyük resmi: Tercih immutable valüzerinde immutable varüzerinde mutable valüzerinde mutable var. Özellikle immutable varbitti mutable val!
Peter Schmitz

2
Yerel bir değişken üzerinden yine de kapatabileceğinizi unutmayın (onu değiştirebilecek yan etkili bir "işlev" sızıntısı gibi) var. Değişmez koleksiyonları kullanmanın bir başka güzel özelliği de, varmutasyona uğramış olsa bile eski kopyaları verimli bir şekilde saklayabilmenizdir .
Gizemli Dan

1
tl; dr: tercih var x: Set[Int] üzerinde val x: mutable.Set[Int] geçtiğiniz takdirde beri xolamaz mutasyon, ilk durumda, diğer bazı işlevine, emin misiniz bu işlevi xsizin için.
pathikrit

17

Değişmez koleksiyonlarla çalışıyorsanız ve bunları "değiştirmeniz" gerekiyorsa, örneğin bunlara bir döngüde öğeler eklemeniz gerekiyorsa, o zaman varelde edilen koleksiyonu bir yerde depolamanız gerektiğinden s kullanmanız gerekir. Yalnızca değişmez koleksiyonlardan okuyorsanız, vals kullanın .

Genel olarak, referansları ve nesneleri karıştırmadığınızdan emin olun. vals değişmez referanslardır (C'de sabit işaretçiler). Kullandığınızda olduğunu, val x = new MutableFoo(), sen mümkün olacak nesneyi değiştirmek için bu xkadar puan ancak mümkün olmayacaktır nesne için değişikliğine x puan. Eğer kullanırsanız tam tersi geçerlidir var x = new ImmutableFoo(). İlk tavsiyemi alırım: Referans noktasını hangi nesneye çevirmeniz gerekmiyorsa, vals kullanın .


1
var immutable = something(); immutable = immutable.update(x)değişmez bir koleksiyon kullanma amacını ortadan kaldırır. Referans şeffaflığından zaten vazgeçtiniz ve genellikle aynı etkiyi daha iyi zaman karmaşıklığına sahip değiştirilebilir bir koleksiyondan elde edebilirsiniz. Dört olasılıktan ( valve vardeğişken ve değişmez) bu en az mantıklı olanıdır. Sık kullanırım val mutable.
Jim Pivarski

3
@JimPivarski Diğerleri gibi ben de katılmıyorum, Daniel'in cevabına ve Peter'ın yorumuna bakın. Bir veri yapısını güncellemeniz gerekiyorsa, değiştirilebilir bir değer yerine değişmez bir değişken kullanmak, yerel varsayımlarınızı bozacak şekilde başkaları tarafından değiştirilme riski olmadan yapıya referanslar sızdırmanız avantajına sahiptir. Bu "diğerleri" için dezavantaj, eski verileri okuyabilmeleridir.
Malte Schwerhoff

Fikrimi değiştirdim ve sana katılıyorum (Orijinal yorumumu tarih için bırakıyorum). O zamandan beri bunu özellikle de kullandım var list: List[X] = Nil; list = item :: list; ...ve bir zamanlar farklı yazdığımı unutmuştum.
Jim Pivarski

@MalteSchwerhoff: Tutarlılık çok önemliyse, programınızı nasıl tasarladığınıza bağlı olarak "eskimiş veri" aslında arzu edilir; Clojure'da eşzamanlılığın nasıl çalıştığına dair temel ilkelerden biri budur.
Erik Kaplun

@ErikAllik Eski verilerin kendiliğinden arzu edildiğini söyleyemem, ancak müşterilerinize vermek istediğiniz / vermeniz gereken garantilere bağlı olarak mükemmel şekilde iyi olabileceği konusunda hemfikirim. Yoksa eski verileri okumanın tek gerçeğinin aslında bir avantaj olduğu bir örneğiniz var mı? Daha iyi performans veya daha basit bir API olabilecek eski verileri kabul etmenin sonuçlarını kastetmiyorum.
Malte Schwerhoff

9

Buna cevap vermenin en iyi yolu bir örnek vermektir. Bir nedenden ötürü sadece sayıları toplamak için bir işlemimiz olduğunu varsayalım. Bu numaraları kaydetmek istiyoruz ve koleksiyonu bunu yapmak için başka bir işleme göndereceğiz .

Elbette, koleksiyonu kaydediciye gönderdikten sonra hala sayı topluyoruz. Diyelim ki günlük kaydı sürecinde, gerçek günlüğe kaydetmeyi geciktiren bazı ek yükler var. Umarım bunun nereye gittiğini görebilirsiniz.

Bu koleksiyonu bir değişken val(değişken, çünkü sürekli eklemekte olduğumuz için) depolarsak , bu, günlüğe kaydetmeyi yapan işlemin, toplama sürecimiz tarafından güncellenmekte olan aynı nesneye bakacağı anlamına gelir . Bu koleksiyon herhangi bir zamanda güncellenebilir ve bu nedenle günlüğe kaydetme zamanı geldiğinde gönderdiğimiz koleksiyonu gerçekten günlüğe kaydetmiyor olabiliriz.

Bir vardeğişmez kullanırsak, kaydediciye değişmez bir veri yapısı göndeririz. Bizim koleksiyonuna fazla numara eklemek, biz olacak yerine Our varbir ile yeni değişmez veri yapısı . Bu, kaydediciye gönderilen koleksiyonun değiştirildiği anlamına gelmez! Hala gönderildiği koleksiyona atıfta bulunuyor. Böylece kaydedicimiz aldığı koleksiyonu gerçekten kaydedecek.


1

Eşzamanlılık senaryolarında hangi kombinasyonun kullanılacağı sorusu daha da önemli hale geldiğinden, bu blog gönderisindeki örneklerin daha fazla ışık tutacağını düşünüyorum: eşzamanlılık için değişmezliğin önemi . Ve biz oradayken, atomicReference gibi senkronize ve @volatile karşılaştırmasının tercih edilen kullanımına dikkat edin: üç araç


-2

var immutable vs. val mutable

Bu soruya birçok mükemmel cevaba ek olarak. İşte olası tehlikeleri gösteren basit bir örnek val mutable:

Değişken nesneler, onları parametre olarak alan yöntemler içinde değiştirilebilir, ancak yeniden atamaya izin verilmez.

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

Sonuç:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

Yeniden var immutableatamaya izin verilmediğinden bunun gibi bir şey olamaz :

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

Sonuçlar:

error: reassignment to val

İşlev parametreleri, valbu yeniden atamaya izin verilmediği için ele alındığından .


Bu yanlış. Bu hatayı almanızın nedeni, ikinci örneğinizde varsayılan olarak değişmez olan Vector'u kullanmanızdır. Bir ArrayBuffer kullanırsanız, onun iyi derlendiğini ve aynı şeyi yeni elemana eklediği ve mutasyona uğramış tamponu yazdırdığı yerde yaptığını göreceksiniz. pastebin.com/vfq7ytaD
EdgeCaseBerg

İlk örnek davranış olduğunu göstermeye çalışıyorum çünkü @EdgeCaseBerg, kasten, benim ikinci örnekte bir Vector kullanıyorum mutable valile mümkün değildir immutable var. Burada yanlış olan ne?
Akavall

Burada elmaları portakallarla karşılaştırıyorsunuz. Vektörün +=dizi tamponu gibi bir yöntemi yoktur . Cevaplarınız, ne olmadığı +=ile aynı olduğunu ima ediyor x = x + y. İşlev parametrelerinin vals olarak değerlendirildiğini belirten ifadeniz doğrudur ve bahsettiğiniz hatayı alırsınız, ancak yalnızca kullandığınız için =. Bir ArrayBuffer ile aynı hatayı alabilirsiniz, böylece koleksiyon değişkenliği burada gerçekten alakalı değildir. Yani iyi bir cevap değil çünkü OP'nin bahsettiği konuya ulaşamıyor. Yine de, istemezseniz etrafta değişken bir koleksiyon geçirmenin tehlikelerine iyi bir örnek.
EdgeCaseBerg

@EdgeCaseBerg Ama benimle aldığım davranışı ArrayBufferkullanarak kopyalayamazsınız Vector. OP'nin sorusu geniş, ancak hangisinin ne zaman kullanılacağına dair öneriler arıyorlardı, bu yüzden cevabımın yararlı olduğuna inanıyorum çünkü değişken koleksiyonun etrafından dolaşmanın tehlikelerini gösteriyor (bu valyardımcı olmuyor); immutable vardaha güvenlidir mutable val.
Akavall
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.