Scala nerelerde etkilerini arar?


398

Bir örtülü Scala için yeni gelenler için soru gibi görünüyor: nereye implicits için derleyici bakmak yapar? Örtük demek istiyorum, çünkü soru hiçbir zaman tam olarak oluşmuş gibi görünmüyor, sanki bunun için kelimeler yokmuş gibi. :-) Örneğin, integralaşağıdaki değerler nereden geliyor?

scala> import scala.math._
import scala.math._

scala> def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit

scala> foo(0)
scala.math.Numeric$IntIsIntegral$@3dbea611

scala> foo(0L)
scala.math.Numeric$LongIsIntegral$@48c610af

İlk sorunun cevabını öğrenmeye karar verenleri takip eden bir başka soru, derleyicinin belli belirsiz durumlarda (ama yine de derleme) hangi örtülü kullanımı nasıl seçeceğidir?

Örneğin, birinden diğerine diğerine scala.Predefiki dönüşüm tanımlar . Bununla birlikte, her iki sınıf da birçok yöntemi paylaşıyor, öyleyse Scala neden çağrı yaparken belirsizlikten şikayet etmiyor ?StringWrappedStringStringOpsmap

Not: Bu soru, sorunu daha genel bir şekilde ifade etme umuduyla, bu diğer sorudan ilham almıştır . Örnek oradan kopyalandı, çünkü cevapta bahsediliyor.

Yanıtlar:


554

Çıkarım Türleri

Scala'daki imalar, tabiri caizse, "otomatik olarak" geçirilebilen bir değeri veya bir türden diğerine otomatik olarak yapılan bir dönüşümü ifade eder.

Örtük Dönüşüm

Tek bir yöntemi çağırırsa, ikincisi türü hakkında çok kısaca konuşan mbir nesne üzerinde obir sınıfın Cve bu sınıf destek yöntemi yok değil m, o zaman Scala gelen bir örtük dönüştürme arayacaktır Cşey yapar desteği m. Basit bir örnek yöntem olacaktır mapile String:

"abc".map(_.toInt)

Stringyöntemi desteklemiyor map, ancak StringOpsyapar ve bir örtük dönüşüm gerçekleşmesi Stringiçin StringOps(bkz mevcut implicit def augmentStringüzerine Predef).

Örtük Parametreler

Öteki örtük tür, örtük parametredir . Bunlar, diğer parametreler gibi yöntem çağrılarına geçirilir, ancak derleyici bunları otomatik olarak doldurmaya çalışır. Eğer yapamazsa şikayet edecektir. Bu parametreleri açık bir şekilde aktarabiliriz, örneğin bu nasıl kullanılır breakOut(örneğin breakOut, bir meydan okuma için hissettiğiniz bir gün hakkındaki soruya bakın ).

Bu durumda, fooyöntem bildirimi gibi örtük bir gereksinim olduğu bildirilmelidir:

def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}

Sınırları Görüntüle

Örtük olanın hem örtük bir dönüşüm hem de örtük bir parametre olduğu bir durum vardır. Örneğin:

def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)

getIndex("abc", 'a')

getIndexSınıfından örtük bir dönüştürme olduğu sürece yöntem herhangi bir nesneyi alabilir Seq[T]. Bu nedenle, bir ' Stringe geçebilirim getIndexve işe yarayacaktır.

Perde arkasında derleyici değiştirir seq.IndexOf(value)için conv(seq).indexOf(value).

Bu çok faydalıdır, bunları yazmak için sözdizimsel şeker vardır. Bu sözdizimsel şekeri kullanarak, getIndexşu şekilde tanımlanabilir:

def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)

Bu sözdizimsel şeker, bir üst sınıra ( ) veya bir alt sınıra ( ) benzer bir görünüm bağlı olarak tarif edilir .CC <: Seq[Int]T >: Null

Bağlam Sınırları

Örtük parametrelerde bir diğer yaygın örüntü tip sınıfı örüntüsüdür . Bu örüntü, onları açıklamayan sınıflara ortak arayüzlerin sağlanmasını sağlar. Hem bir köprü modeli - hem de endişelerin ayrılmasını sağlamak - hem de bir adaptör deseni olarak hizmet edebilir.

IntegralBahsettiğiniz sınıf tipi sınıfı modelinin klasik bir örneğidir. Scala'nın standart kütüphanesindeki diğer bir örnek Ordering. Scalaz adı verilen bu kalıbı yoğun kullanan bir kütüphane var.

Bu kullanımının bir örneğidir:

def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

Bir denir bunun için sözdizimsel şeker de bulunmaktadır bağlı bağlam örtülü başvurmak için ihtiyaç tarafından daha az yararlı yapılır. Bu yöntemin düz bir dönüşümü şöyle görünür:

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

Bağlam sınırları, yalnızca bunları kullanan diğer yöntemlere aktarmanız . Örneğin, yöntemin örtük sortedolması Seqgerekir Ordering. Bir yöntem oluşturmak için aşağıdakiler reverseSortyazılabilir:

def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse

Çünkü Ordering[T]örtülü geçildi reverseSort, o zaman için örtülü iletebiliriz sorted.

Etkiler nereden geliyor?

Derleyici, nesnenin sınıfında bulunmayan bir yöntemi çağırdığınız veya örtük bir parametre gerektiren bir yöntemi çağırdığınız için örtük bir gereksinim gördüğünde, ihtiyaca uygun bir örtük arar .

Bu arama, hangi etkilerin görünür ve hangilerinin görünmediğini tanımlayan belirli kurallara uyar. Derleyicinin çıkarımları nerede arayacağını gösteren aşağıdaki tablo , Josh Suereth tarafından yazılan Scala bilgisini geliştirmek isteyen herkese yürekten tavsiye ettiğim mükemmel bir sunumdan alınmıştır . O zamandan beri geri bildirim ve güncellemelerle tamamlandı.

Aşağıdaki 1 numara altında mevcut olan çıkarımlar 2 numara altındakilere göre daha önceliklidir. Bunun dışında, örtük parametrenin türüyle eşleşen birkaç uygun argüman varsa, statik aşırı yüklenme çözünürlüğü kuralları kullanılarak en spesifik olanı seçilecektir (bakınız Scala) Şartname §6.26.3). Daha ayrıntılı bilgi, bu cevabın sonunda bağlantı verdiğim bir soruda bulunabilir.

  1. Mevcut kapsamda ilk bakış
    • Mevcut kapsamda tanımlanan çıkarımlar
    • Açık ithalat
    • joker ithalat
    • Diğer dosyalarda aynı kapsam
  2. Şimdi ilişkili türlere bakın
    • Bir türün tamamlayıcı nesneleri
    • Bağımsız değişken türünün örtük kapsamı (2.9.1)
    • Kapalı tür argümanlarının kapsamı (2.8.0)
    • İç içe tipler için dış nesneler
    • Diğer boyutlar

Onlara bazı örnekler verelim:

Mevcut Kapsamda Tanımlanan Çıkarımlar

implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope

Açık İthalatlar

import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM")    // implicit conversion from Java Map to Scala Map

Joker Karakter İçe Aktarma

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

Diğer Dosyalarda Aynı Kapsam

Edit : Bu farklı bir önceliği yok gibi görünüyor. Öncelik ayrımı gösteren bir örneğiniz varsa, lütfen yorum yapın. Aksi takdirde, buna güvenmeyin.

Bu ilk örnek gibidir, ancak örtülü tanımın kullanımından farklı bir dosyada olduğu varsayılarak. Nesnelerin nasıl paketlendiğine de bakın çıkarımlarda bulunmak için kullanılabileceğini .

Bir Türün Tamamlayıcı Nesneleri

Burada iki nesne arkadaşı var. İlk olarak, "kaynak" türünün nesne arkadaşı incelenir. Örneğin, nesnenin içinde Optionörtük bir dönüştürme vardır Iterable, böylece bir kişi Iterableyöntemleri arayabilir Optionveya Optionbir Iterable. Örneğin:

for {
    x <- List(1, 2, 3)
    y <- Some('x')
} yield (x, y)

Bu ifade derleyici tarafından şu dile çevrilir:

List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))

Ancak, List.flatMapbir beklediğini TraversableOnce, hangi Optiondeğildir. Derleyici daha sonra Optionnesnenin arkadaşına bakar ve bu ifadeyi, bu ifadeyi doğru yapan Iterablea dönüşümü bulur TraversableOnce.

İkinci olarak, beklenen türde tamamlayıcı nesne:

List(1, 2, 3).sorted

Yöntem sorted, örtük bir yöntem alır Ordering. Bu durumda, nesnenin içine bakar Ordering, sınıfa eşlik eder Orderingve Ordering[Int]orada bir örtük bulur .

Süper sınıfların eşlik eden nesnelerine de bakıldığını unutmayın. Örneğin:

class A(val n: Int)
object A { 
    implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b  // s == "A: 2"

Bu Scala örtük nasıl bulduğunu olduğunu Numeric[Int]ve Numeric[Long]bunların içinde bulanan olarak, bu arada, sorunuzu Numericdeğil Integral.

Bağımsız Değişken Türünün Kapalı Kapsamı

ABağımsız değişken türünde bir yönteminiz varsa , örtük tür kapsamı Ada dikkate alınacaktır. "Örtük kapsam" ile, tüm bu kuralların yinelemeli olarak uygulanacağını kastediyorum - örneğin, tamamlayıcı nesnesi Ayukarıdaki kurala göre çıkarımlar için aranacaktır.

Bunun, örtük kapsamının Ao parametrenin dönüşümlerinin değil, tüm ifadenin aranacağı anlamına geldiğini unutmayın . Örneğin:

class A(val n: Int) {
  def +(other: A) = new A(n + other.n)
}
object A {
  implicit def fromInt(n: Int) = new A(n)
}

// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)

Bu Scala 2.9.1'den beri mevcuttur.

Tür Bağımsız Değişkenlerinin Kapalı Kapsamı

Tür sınıfı modelinin gerçekten çalışması için bu gereklidir. OrderingÖrneğin, şunu düşünün : Tamamlayıcı nesnesinde bazı çıkarımlarla birlikte gelir, ancak buna bir şeyler ekleyemezsiniz. Peki Orderingotomatik olarak bulunan kendi sınıfınız için nasıl bir yapabilirsiniz ?

Uygulama ile başlayalım:

class A(val n: Int)
object A {
    implicit val ord = new Ordering[A] {
        def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
    }
}

Yani, aradığınızda ne olacağını düşünün

List(new A(5), new A(2)).sorted

Gördüğümüz gibi, yöntem bir sortedbekler Ordering[A](aslında Ordering[B], nerede, bekler B >: A). İçinde böyle bir şey Orderingyok ve bakılacak bir "kaynak" türü yok. Açıkçası, içeride bu aralar Abir olan tip argüman arasında Ordering.

Bu aynı zamanda çeşitli toplama yöntemlerinin nasıl CanBuildFromçalışacağını da gösterir: etkileri, tamamlayıcı nesnelerin içinde tür parametrelerine bulunur CanBuildFrom.

Not :, bir tür parametresi olduğu Orderingşeklinde tanımlanır . Daha önce, Scala'nın çok anlamlı olmayan tip parametrelerine baktığını söyledim. Yukarıda aranan örtüktrait Ordering[T]TOrdering[A] yerlerde, Aparametrenin yazın değil, gerçek bir türüdür: bir olan tip argüman için Ordering. Scala teknik özellikleri bölüm 7.2'ye bakınız.

Bu Scala 2.8.0'dan beri mevcuttur.

Yuvalanmış Türler için Dış Nesneler

Aslında bunun örneklerini görmedim. Birisi paylaşabilirse minnettar olurum. İlke basit:

class A(val n: Int) {
  class B(val m: Int) { require(m < n) }
}
object A {
  implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b  // s == "B: 3"

Diğer Ölçüler

Eminim ki bu bir şakaydı ama bu cevap güncel olmayabilir. Bu soruyu, olup bitenlerin son hakemi olarak almayın ve eğer güncelliğini yitirdiğini fark ederseniz, lütfen düzeltebilmem için beni bilgilendirin.

DÜZENLE

İlgili ilgi alanları:


60
Cevaplarınızı bir kitapta kullanmaya başlamanın zamanı geldi, şimdiye kadar sadece hepsini bir araya getirme meselesi.
pedrofurla

3
@pedrofurla Portekizce bir kitap yazmayı düşündüm. Birisi bana teknik bir yayıncı ile temas kurabilirse ...
Daniel C. Sobral

2
Türün bölümlerinin tamamlayıcılarının paket nesneleri de aranır. lampsvn.epfl.ch/trac/scala/ticket/4427
retronym

1
Bu durumda, örtük kapsamın bir parçasıdır. Çağrı sitesinin bu paket içinde olması gerekmez. Bu benim için şaşırtıcıydı.
retronym

2
Evet, bu yüzden stackoverflow.com/questions/8623055 bunu özellikle kapsıyor, ancak "Aşağıdaki liste öncelik sırasına göre sunuluyor ... lütfen rapor edin." Temel olarak, iç listeler hepsinin eşit ağırlığa sahip olması nedeniyle sıralanmamış olmalıdır (en az 2.10'da).
Eugene Yokota

23

Örtülü parametre çözünürlüğünün önceliğini bulmak istedim, sadece nerede göründüğünü değil, bu yüzden ithalat vergisi (ve bazı geri bildirimlerden sonra tekrar örtük parametre önceliği) olmadan etkileri yeniden gözden geçiren bir blog yazısı yazdım .

İşte liste:

  • 1) ön bildirim olmadan erişilebilen yerel bildirim, ithalat, dış kapsam, miras, paket nesnesi aracılığıyla mevcut çağırma kapsamı tarafından görülebilir imalar.
  • 2) aradığımız implicit'in türüyle bir miktar ilişkisi olan her türlü tamamlayıcı nesneyi ve paket nesnesini içeren örtük kapsam (yani, türün paket nesnesi, türün kendisinin tür nesnesi, varsa tür yapıcısının) parametrelerini ve ayrıca süper tip ve süper portlarının).

Her iki aşamada da bunu çözmek için birden fazla örtük, statik aşırı yükleme kuralı kullanılırsa.


3
Yalnızca paketleri, nesneleri, özellikleri ve sınıfları tanımlayan ve kapsamı belirttiğinizde harflerini kullanan bir kod yazdıysanız bu geliştirilebilir. Herhangi bir yöntem beyanı koymaya gerek yok - sadece isimleri ve kimleri ve kimin kapsamını genişletir.
Daniel C.Sobral
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.