Örnek neden derlenmiyor, yani (ortak-, karşı- ve içe-) varyans nasıl çalışır?


147

İtibaren ardından bu soruya , kutu birisi Scala aşağıdaki açıklayabilir:

class Slot[+T] (var some: T) { 
   //  DOES NOT COMPILE 
   //  "COVARIANT parameter in CONTRAVARIANT position"

}

Ben arasındaki farkı anlamaya +Tve Ttip beyanı (kullandığım eğer derler de T). Ama o zaman kişi, parametresiz bir şeyi yaratmaya başvurmadan kendi tür parametresinde kovaryant olan bir sınıfı nasıl yazar ? Aşağıdakilerin yalnızca bir örneği ile oluşturulabilmesini nasıl sağlayabilirim T?

class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}

EDIT - şimdi bunu aşağıya indirdi:

abstract class _Slot[+T, V <: T] (var some: V) {
    def getT() = { some }
}

bu iyi, ama şimdi sadece bir tane istiyorum iki tip parametreleri var. Bu yüzden soruyu tekrar soracağım:

Kendi türünde kovaryant olan değişmez bir Slot sınıfı nasıl yazabilirim ?

DÜZENLEME 2 : Duh! Ben kullandım var, değil val. İstediğim şudur:

class Slot[+T] (val some: T) { 
}

6
Çünkü varayarlanamazken ayarlanabilir val. Skala'nın değişmez koleksiyonlarının kovaryant olduğu, ancak değişebilen koleksiyonların aynı olmasının nedeni de aynıdır.
oxbow_lakes

Bu, bu bağlamda ilginç olabilir: scala-lang.org/old/node/129
user573215

Yanıtlar:


302

Genel olarak, bir kovaryant tipi parametre, sınıf alt tiplendiğinde değişmesine izin verilen bir parametredir (alternatif olarak alt tiplemeye göre değişir, dolayısıyla "co-" öneki). Daha somut olarak:

trait List[+A]

List[Int]bir alttür List[AnyVal]çünkü Intbir alttür AnyVal. Bu List[Int], bir tür değerin ne zaman List[AnyVal]beklendiğinin bir örneğini sağlayabileceğiniz anlamına gelir . Bu, jeneriklerin çalışması için gerçekten çok sezgisel bir yoldur, ancak değişebilir verilerin varlığında kullanıldığında sesinin (tip sistemini bozduğu) ortaya çıkıyor. Bu nedenle jenerikler Java'da değişmezdir. Java dizilerini (yanlış bir şekilde kovaryant olan) kullanarak ortaya çıkan sessizliğe kısa bir örnek:

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

Bir tür Stringdiziye yalnızca tür değeri atadık Integer[]. Açık olması gereken nedenlerden dolayı, bu kötü bir haber. Java'nın tür sistemi aslında derleme zamanında buna izin verir. JVM ArrayStoreExceptionçalışma zamanında "yardımcı olur" . ArraySınıfta type parametresi değişmez olduğu için Scala'nın tür sistemi bu sorunu önler (bildirim [A]yerine [+A]).

Kontravaryans olarak bilinen başka bir varyans türü olduğuna dikkat edin . Kovaryansın neden bazı sorunlara neden olabileceğini açıkladığı için bu çok önemlidir. Çelişki, kelimenin tam anlamıyla kovaryansın tersidir: parametreler alt tiplemeyle yukarı doğru değişir . Kısmen çok daha az yaygındır çünkü çok sezgiseldir, ancak çok önemli bir uygulamaya sahiptir: fonksiyonlar.

trait Function1[-P, +R] {
  def apply(p: P): R
}

Type parametresindeki " - " sapma notuna dikkat edin P. Bir bütün olarak bu deklarasyon Function1, çelişkili Pve içinde kovaryant anlamına gelir R. Böylece, aşağıdaki aksiyomları türetebiliriz:

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

Bunun T1'bir alt türü (veya aynı tür) olması gerektiğine dikkat edin T1, oysa T2ve için tersidir T2'. İngilizce olarak, bu aşağıdaki gibi okunabilir:

Bir fonksiyon bir başka fonksiyon bir alt tipi olan B parametre türü, eğer A parametresi tipte bir süper tip olan B dönüş türü ise A dönüş türü bir alt tipi olan B .

Bu kuralın nedeni okuyucuya bir alıştırma olarak bırakılmıştır (ipucu: yukarıdaki örnek dizim gibi işlevler alt tür olarak yazıldığında farklı durumlar hakkında düşünün).

Yeni bulunan eş ve karşıtlık bilginizle, aşağıdaki örneğin neden derlenmeyeceğini görebilmelisiniz:

trait List[+A] {
  def cons(hd: A): List[A]
}

Sorun Akovaryant, consfonksiyon ise type parametresinin değişmez olmasını bekliyor . Böylece Ayanlış yönü değiştiriyor. İlginçtir ki, bu sorunu Listçelişkili hale getirerek çözebiliriz A, ancak daha sonra işlev dönüş türünün kovaryant olmasını beklediği için dönüş türü List[A]geçersiz olur .cons

Buradaki sadece iki seçeneğimiz şunlardır: a) Adeğişmez yapmak , kovaryansın hoş, sezgisel alt yazma özelliklerini kaybetmek veya b) alt sınır olarak constanımlanan yönteme yerel bir tür parametresi eklemek A:

def cons[B >: A](v: B): List[B]

Bu artık geçerli. Bunun Aaşağı doğru değiştiğini, ancak alt sınırından beri Byukarı doğru değişebildiğini hayal edebilirsiniz . Bu yöntem beyanı ile kovaryant olabiliriz ve her şey yolunda gider.AAA

Bu hile yalnızca List, daha az spesifik türde bir örneği döndürdüğümüzde işe yaradığına dikkat edin B. Değişken hale getirmeye çalışırsanız , derleyici tarafından izin verilmeyen bir tür değişkenine Listtür değerleri atamaya çalıştığınız için işler bozulur. Değişebilirliğe sahip olduğunuzda, belirli bir tipte (erişimciyle birlikte) değişmezlik anlamına gelen bir yöntem parametresi gerektiren bir çeşit mutasyona sahip olmanız gerekir. Kovaryans değişmez verilerle çalışır, çünkü tek olası işlem kovaryant dönüş tipi verilebilen bir erişimcidir.BA


4
Bu basit ingilizce olarak belirtilebilir mi - parametre olarak daha basit bir şey alabilir ve daha karmaşık bir şey döndürebilirsiniz?
Phil

1
Java derleyicisi (1.7.0) "Object [] arr = new int [1];" derlemiyor bunun yerine hata iletisini verir: "java: uyumsuz türler gerekli: java.lang.Object [] bulundu: int []". Bence "Object [] arr = new Integer [1];" demek istediniz.
Emre Sevinç

2
"Bu kuralın nedeni okuyucuya bir alıştırma olarak bırakılmıştır (ipucu: yukarıdaki örnek dizim gibi, işlevler alt tür olarak yazıldığında farklı durumlar hakkında düşünün"). Aslında birkaç örnek verebilir misiniz?
perryzheng

2
başına @perryzheng bu , almak trait Animal, trait Cow extends Animal, def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)ve def iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a). O zaman, iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})hayvan çobanımız inek sürüsü yapabilir, ancak iNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})inek çobanımız tüm hayvanları süremez, çünkü derleme hatası verir.
Lasf

Bu ilgili ve bana varyans konusunda yardımcı oldu: typelevel.org/blog/2016/02/04/variance-and-functors.html
Peter Schmitz

27

@ Daniel bunu çok iyi açıkladı. Ancak kısaca açıklamak gerekirse, izin verildiyse:

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.getdaha sonra Animal, Dog(duh!) 'ya dönüştürülemediğinden çalışma zamanında bir hata atar .

Genel olarak değişebilirlik, eş-varyans ve kontra-varyans ile iyi gitmez. Tüm Java koleksiyonlarının değişmez olmasının nedeni budur.


7

Bkz örnekle Scala bu tam bir tartışma için, sayfa 57 +.

Yorumunuzu doğru bir şekilde anlıyorsam, sayfa 56'nın altından başlayan bölümü tekrar okumanız gerekir (temel olarak, sorduğunuz şey çalışma süresi kontrolleri olmadan tür güvenli değildir, hangi scala yapmaz, böylece şansınız kalmaz). Yapınızı kullanmak için örneklerini çevirme:

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

Sorunuzu anlamadığımı düşünüyorsanız (ayrı bir olasılık), sorun tanımına daha fazla açıklama / bağlam eklemeyi deneyin ve tekrar deneyeceğim.

Düzenlemenize yanıt olarak: Değişmez yuvalar tamamen farklı bir durumdur ... * gülümseme * Umarım yukarıdaki örnek yardımcı olmuştur.


Bunu okudum; ne yazık ki (hala) yukarıda istediğimi nasıl yapabileceğimi anlamıyorum (yani aslında T'de parametreli bir sınıfsal kovaryant
yazıyorum

Bunun biraz sert olduğunu fark ettiğimde downmarkımı kaldırdım. Scala'dan bitleri örnek olarak okuduğum soruda açıkça belirtmeliydim; Ben sadece "daha az resmi" bir şekilde açıklamak istedim
oxbow_lakes

@oxbow_lakes smile Korkarım Scala Örnek olarak daha az resmi açıklamadır. En iyisi, burada çalışmak için somut örnekler kullanmaya çalışabiliriz ...
MarkusQ

Maalesef, yuvamın değiştirilebilir olmasını istemiyorum. Ben sadece sorunu var ve val değil ilan
oxbow_lakes

3

Parametreye bir alt sınır uygulamanız gerekir. Sözdizimini hatırlamakta zorlanıyorum, ancak bunun böyle bir şey olacağını düşünüyorum:

class Slot[+T, V <: T](var some: V) {
  //blah
}

Örnek Scala'yı anlamak biraz zor, birkaç somut örnek yardımcı olabilirdi.

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.