2010-01-16 13 views
13

:Neden Seq.contains türü kabul eder A tipi parametresi yerine Herhangi bir? Örneğin

scala> val l:List[String] = List("one", "two") 
l: List[String] = List(one, two) 

scala> l.contains(1) //wish this didn't compile 
res11: Boolean = false 

The various explanations of why things were done this way in Java Harita ve Set contains ve arkadaş tipi uyumlu sürümü uygularım gibi pek buraya uygulamak için görünmüyor. Bir küme içine klonlama kısa bir tür contains tür güvenli yapmak için herhangi bir yolu var mı?

cevap

30

Sorun, bu parametresinde Seq'un eşanlamlıdır. Bu, işlevselliğinin büyük çoğunluğu için çok mantıklıdır. Değişken bir konteyner olarak, eşdeğeri olmalıdır. Ne yazık ki, bu, parametrelenmiş tipin bir kısmını alan bir yöntem tanımlamak zorunda kaldığında ortaya çıkıyor. Aşağıdaki örneği inceleyelim:

trait Seq[+A] { 
    def apply(i: Int): A  // perfectly valid 

    def contains(v: A): Boolean // does not compile! 
} 

sorun fonksiyonları her zaman kendi dönüş türleri kendi parametre türleri ve kovaryant içinde kontravaryant olmasıdır. Böylece, apply yöntemi A türünde bir değer döndürebilir, çünkü A, apply için dönüş türüyle birlikte değişkendir. Bununla birlikte, , türünde A türünde bir değer alamıyor, çünkü bu parametrenin kontravaryant olması gerekir.

Bu sorun, farklı şekillerde çözülebilir. Bir seçenek A'u değişmez tip parametresi yapmaktır. Bu, hem kovaryant hem de kontravaryant bağlamlarda kullanılmasına izin verir. Bununla birlikte, bu tasarım Seq[String]'un 'un Seq[Any] alt tipi olması anlamına gelir. Diğer bir seçenek (ve en sık kullanılanı), aşağıda eşdeğer tipte sınırlanan bir yerel tip parametresi kullanmaktır. Örneğin: heterojen kapları kullanan bir kod yazarken bazı çok sezgisel sonuçlar sağlar olarak

trait Seq[+A] { 
    def +[B >: A](v: B): Seq[B] 
} 

Bu numara yanı Seq[String] <: Seq[Any] özelliğini korur. Örneğin: Any çünkü

val s: Seq[String] = ... 
s + 1  // will be of type Seq[Any] 

bu örnekte + fonksiyonunun sonucu, tip Seq[Any] bir değeri olan en üst sınır tip String ve Int için (LUB) (diğer bir deyişle, en az -komik süper tip). Bunu düşünürseniz, tam da beklediğimiz davranış budur.Hem String hem de Int bileşenleriyle bir dizi oluşturursanız, türüSeq[Any] olmalıdır.

Maalesef bu hile, contains gibi yöntemlerle uygulanabilir iken, bazı şaşırtıcı sonuçlar üretir:

trait Seq[+A] { 
    def contains[B >: A](v: B): Boolean // compiles just fine 
} 

val s: Seq[String] = ... 
s contains 1  // compiles! 

Buradaki sorun biz tip Int değerinin iletilmesi contains yöntemini çağırarak olmasıdır. Scala bunu görür ve Int ve A'un bir üst türü olan B için bir tür çıkarmaya çalışır, bu durumda String olarak başlatılır. Bu iki tür için LUB, Any (daha önce gösterildiği gibi) ve contains için yerel tür örneklemesi Any => Boolean olacaktır. Böylece,yönteminin güvenli olmaması için görünür.

ikisi de kendi parametre türlerinde covariant çünkü Bu sonuç Map veya Set için bir sorun değildir:

trait Map[K, +V] { 
    def contains(key: K): Boolean // compiles 
} 

trait Set[A] { 
    def contains(v: A): Boolean  // also compiles 
} 

Yani, uzun lafın kısası, kovaryant konteyner türlerinde contains yöntem ile sınırlı tutulamaz Fonksiyon tiplerinin çalışma şeklinden dolayı sadece bileşen tipinin değerlerini alır (parametre tiplerinde kontravaryant). Bu gerçekten Scala veya kötü bir uygulama sınırlaması değildir, matematiksel bir gerçektir.

Teselli ödülü, bu gerçekten pratikte bir sorun değil. Ve diğer cevapların da belirttiği gibi, gerçekten gerçekten ek kontrolüne ihtiyacınız varsa, "tip-güvenli" contains benzeri bir yöntem ekleyerek kendi örtük dönüşümünüzü tanımlayabilirsiniz. tip eşitlik için bir delil temin By

+0

Derleyiciye tip gereksinimlerini karşılamak için yalnızca resmi olarak orada bulunan ve belirli bir bağlamda daraltılabilen parametreler hakkında bilgi veren bir mekanizmanın olmaması üzücü bir durumdur. Liste [B] 'nin sadece B'yi içerebileceği doğrudur, fakat aynı zamanda Liste [B]' nin Liste [A] olarak yeniden yorumlanabileceği doğrudur, burada B <: A ve daha sonra (yaklaşık) sorulabilir. –

+1

Burada olan tam olarak budur. “Seq [String]' 'Seq [Any]' olarak yeniden yorumlanır ve Int'ten (: Any) sonra Int (Int) hakkında soru sorabiliriz. –

+0

Bu, matematiksel bir olguya dönüşen matematiği görmeyi çok isterim (üstelik kontravaryant olan üsselerin ötesinde). –

4

Neden böyle şeylerin bu şekilde tasarlandığından emin değilim - muhtemelen Java'da bir şeyi yansıtmak. Muhtemelen tek içine bu malzeme ve diğer kullanışlı böyle şeyler koymak isteyeceksiniz (

class SeqWithHas[T](s: Seq[T]) { 
    def has(t: T) = s.contains(t) 
} 
implicit def seq2seqwithhas[T](s: Seq[T]) = new SeqWithHas(s) 

scala> List("3","5") has 1 
<console>:7: error: type mismatch; 
found : Int(1) 
required: java.lang.String 
     List("3","5") has 1 
         ^

scala> List("3","5") has "1" 
res1: Boolean = false 

:

Neyse, bir dizi içine klonlamak için daha pezevenk-my-kütüphane desen kullanmak daha verimlidir nesne ve daha sonra kaynak dosyalarının çoğunda MyHandyObject._ içe aktarın.)

3

Düzenli bir yöntem çağrısı kullanmak için infix yüklemek istiyorsanız, aşağıdakileri tanımlayıp içe aktarın (...) yöntemi oluşturmaktan kaçınacaksınız. bir örnek her defasında tip güvenli "has" testine ihtiyaç duyar (örn. iç döngülerde faydalıdır):

def has[T](s: Set[T], t: T) = s.contains(t) 

Doğal olarak, [T] ayarı, içerme yöntemine sahip en az belirli bir türe rahatlayabilir.

+0

Bu yorum alanı kodunu göndermek için çok küçük, ama optimizasyonlar infix yöntemi yalnızca küçük bir yavaşlamaya neden izin de oldukça iyi: örtük yöntem (uzunluk 3 listesinde 2M testleri): 1247 vs 1141; 1215 vs 1076; 1184 vs 1091; 1181 vs 1087; 1180 ve 1085; 1183 vs 1095; 1200 vs 1103; 1203 vs 1093; 1183 vs 1088; 1188 vs 1087. Çok kötü değil - örtülü nesne oluşturma için yaklaşık% 10 ek yük. –

0

,

def contains[A,B](xs: List[A], x: B)(implicit ev: A =:= B) = xs.contains(x) 
İlgili konular