Örtülü dönüştürme ve tür sınıfı


95

Scala'da, mevcut veya yeni tipleri güçlendirmek için en az iki yöntem kullanabiliriz. Diyelim ki bir şeyin bir Int. Aşağıdaki özelliği tanımlayabiliriz.

Örtük dönüştürme

trait Quantifiable{ def quantify: Int }

Ve sonra ör. Dizeler ve Listeler'i ölçmek için örtük dönüştürmeleri kullanabiliriz.

implicit def string2quant(s: String) = new Quantifiable{ 
  def quantify = s.size 
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{ 
  val quantify = l.size 
}

Bunları içe aktardıktan sonra quantify, dizge ve listeler üzerinde yöntemi çağırabiliriz . Ölçülebilir listenin uzunluğunu sakladığını ve böylece sonraki aramalarda listenin pahalı geçişini önlediğini unutmayın quantify.

Tip sınıfları

Bir alternatif, Quantified[A]bazı tiplerin Aölçülebileceğini belirten bir "tanık" tanımlamaktır .

trait Quantified[A] { def quantify(a: A): Int }

Daha sonra bu tür sınıfın örneklerini için Stringve bir Listyerde sağlıyoruz.

implicit val stringQuantifiable = new Quantified[String] {
  def quantify(s: String) = s.size 
}

Ve sonra argümanlarını ölçmesi gereken bir yöntem yazarsak, şunu yazarız:

def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) = 
  as.map(ev.quantify).sum

Veya bağlama bağlı sözdizimini kullanarak:

def sumQuantities[A: Quantified](as: List[A]) = 
  as.map(implicitly[Quantified[A]].quantify).sum

Ama hangi yöntem ne zaman kullanılmalı?

Şimdi soru geliyor. Bu iki kavram arasında nasıl karar verebilirim?

Şimdiye kadar ne fark ettim.

tip sınıfları

  • tür sınıfları güzel bağlam bağlantılı sözdizimine izin verir
  • tür sınıflarıyla her kullanımda yeni bir sarmalayıcı nesne oluşturmuyorum
  • tür sınıfının birden çok tür parametresi varsa, bağlama bağlı sözdizimi artık çalışmaz; Bir şeyleri sadece tamsayılarla değil, bazı genel tipteki değerlerle ölçmek istediğimi hayal edin T. Bir tür sınıfı oluşturmak isterdimQuantified[A,T]

örtük dönüştürme

  • yeni bir nesne oluşturduğum için, orada değerleri önbelleğe alabilir veya daha iyi bir temsil hesaplayabilirim; ancak bundan kaçınmalı mıyım, çünkü bu birkaç kez olabilir ve açık bir dönüştürme muhtemelen yalnızca bir kez çağrılabilir?

Bir cevaptan ne bekliyorum

Her iki kavram arasındaki farkın önemli olduğu bir (veya daha fazla) kullanım durumunu sunun ve neden birini diğerine tercih ettiğimi açıklayın. Ayrıca iki kavramın özünü ve birbirleriyle ilişkilerini açıklamak, örnek olmasa bile güzel olurdu.


Tür sınıfları bağlam sınırlarını kullansa da, "görünüm sınırından" bahsettiğiniz tür sınıfı noktalarında bazı karışıklıklar vardır.
Daniel C. Sobral

1
+1 mükemmel soru; Buna kapsamlı bir cevap vermekle çok ilgileniyorum.
Dan Burton

@Daniel Teşekkürler. Ben hep yanlış anlarım.
ziggystar

2
Bir yerde yanılıyorsun: İkinci örtük dönüştürme örneğinde saklamak sizebir değer listesinin ve ölçmek için sonraki çağrılarda listenin pahalı geçişi önler demek, ama için her çağrıda tetiklendiğinde böylece mülkü yeniden canlandırıyor ve yeniden hesaplıyor . Demek istediğim, sonuçları örtük dönüştürmelerle önbelleğe almanın aslında bir yolu olmadığıdır. quantifylist2quantifiableQuantifiablequantify
Nikita Volkov

@NikitaVolkov Gözleminiz doğru. Ve bunu ikinci ve son paragraf arasındaki sorumda ele alıyorum. Önbelleğe alma, dönüştürülen nesne bir dönüştürme yöntemi çağrısından sonra daha uzun süre kullanıldığında (ve belki de dönüştürülmüş biçiminde aktarıldığında) çalışır. Tür sınıfları muhtemelen daha derine inerken dönüştürülmemiş nesne boyunca zincirlenir.
ziggystar

Yanıtlar:


42

Scala In Depth'ten materyalimi kopyalamak istemesem de, yazım sınıflarının / tip özelliklerinin sonsuz derecede daha esnek olduğunu belirtmeye değer olduğunu düşünüyorum.

def foo[T: TypeClass](t: T) = ...

varsayılan bir tür sınıfı için yerel ortamında arama yeteneğine sahiptir. Ancak, varsayılan davranışı istediğim zaman şu iki yoldan biriyle geçersiz kılabilirim:

  1. Örtülü aramaya kısa devre yapmak için Kapsam'da örtük bir tür sınıf örneği oluşturma / içe aktarma
  2. Bir tür sınıfını doğrudan geçmek

İşte bir örnek:

def myMethod(): Unit = {
   // overrides default implicit for Int
   implicit object MyIntFoo extends Foo[Int] { ... }
   foo(5)
   foo(6) // These all use my overridden type class
   foo(7)(new Foo[Int] { ... }) // This one needs a different configuration
}

Bu, tür sınıflarını çok daha esnek hale getirir. Başka bir şey de, tür sınıflarının / özelliklerinin örtük aramayı daha iyi desteklemesidir .

İlk örneğinizde, örtük bir görünüm kullanıyorsanız, derleyici aşağıdakiler için örtük bir arama yapar:

Function1[Int, ?]

Function1Eşlik eden nesneye ve eşlik eden nesneye bakar Int.

Bildirim Quantifiableolduğu hiçbir yerde örtülü aramada. Bu, örtük görünümü bir paket nesnesine yerleştirmeniz veya kapsama aktarmanız gerektiği anlamına gelir . Neler olduğunu hatırlamak daha çok iş.

Öte yandan, bir tür sınıfı açıktır . Yöntem imzasında ne aradığını görürsünüz. Ayrıca örtük bir aramanız var

Quantifiable[Int]

bu, Quantifiable'ın tamamlayıcı nesnesine ve Int ' nin eşlik eden nesnesine bakar . Bu, varsayılanları sağlayabileceğiniz ve yeni türler (bir MyStringsınıf gibi ) tamamlayıcı nesnelerinde bir varsayılan sağlayabileceği ve dolaylı olarak aranacağı anlamına gelir.

Genelde tip sınıfları kullanıyorum. İlk örnek için sonsuz derecede daha esnektirler. Örtük dönüştürmeleri kullandığım tek yer, bir Scala sarıcı ve bir Java kitaplığı arasında bir API katmanı kullanırken ve dikkatli değilseniz bu bile 'tehlikeli' olabilir.


"Sonsuz derecede daha esnek" hakkında
hm

20

Oyuna girebilecek kriterlerden biri, yeni özelliğin nasıl "hissetmesini" istediğinizdir; örtük dönüştürmeleri kullanarak, bunun sadece başka bir yöntemmiş gibi görünmesini sağlayabilirsiniz:

"my string".newFeature

... tür sınıflarını kullanırken, her zaman harici bir işlevi çağırdığınız gibi görünecektir:

newFeature("my string")

Örtülü dönüştürmelerle değil, tür sınıflarıyla elde edebileceğiniz bir şey, bir türün örneğinden ziyade bir türe özellikler eklemektir . Daha sonra, kullanılabilir türün bir örneğine sahip olmasanız bile bu özelliklere erişebilirsiniz. Kanonik bir örnek şöyle olabilir:

trait Default[T] { def value : T }

implicit object DefaultInt extends Default[Int] {
  def value = 42
}

implicit def listsHaveDefault[T : Default] = new Default[List[T]] {
  def value = implicitly[Default[T]].value :: Nil
}

def default[T : Default] = implicitly[Default[T]].value

scala> default[List[List[Int]]]
resN: List[List[Int]] = List(List(42))

Bu örnek aynı zamanda kavramların birbiriyle nasıl sıkı bir şekilde ilişkili olduğunu da gösterir: tür sınıfları, sonsuz sayıda örneğini üretecek bir mekanizma olmasaydı, neredeyse kullanışlı olmazdı; implicityöntem olmadan (kuşkusuz bir dönüştürme değil), yalnızca sonlu sayıda türün özelliğe sahip olabilirdim Default.


@Phillippe - Yazdığınız teknikle çok ilgileniyorum ... ama Scala 2.11.6'da çalışmıyor gibi görünüyor. Cevabınızın güncellenmesini isteyen bir soru yayınladım. yardımcı olabilirseniz şimdiden teşekkürler: Lütfen
Chris Bedford

@ChrisBedford defaultGelecekteki okuyucular için tanımını ekledim .
Philippe

13

İki teknik arasındaki farkı, sadece adlandırılmış bir sarmalayıcıyla, işlev uygulamasına benzeterek düşünebilirsiniz. Örneğin:

trait Foo1[A] { def foo(a: A): Int }  // analogous to A => Int
trait Foo0    { def foo: Int }        // analogous to Int

A => IntBirincisinin bir örneği, bir tür işlevi kapsarken, ikincisinin bir örneği zaten bir A. Desene devam edebilirsin ...

trait Foo2[A, B] { def foo(a: A, b: B): Int } // sort of like A => B => Int

bu nedenle , bir örneğe Foo1[B]kısmi uygulama gibi düşünebilirsiniz . Bunun harika bir örneği Miles Sabin tarafından "Scala'da İşlevsel Bağımlılıklar" olarak yazılmıştır .Foo2[A, B]A

Yani gerçekten benim açımdan prensip olarak şu:

  • Bir sınıfı "pezevenkleştirmek" (örtük dönüştürme yoluyla) "sıfırıncı sıra" durumudur ...
  • tip sınıfını bildirmek "birinci dereceden" durumdur ...
  • fundeps içeren çok parametreli tip sınıfları (veya fundeps gibi) genel durumdur.
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.