Scala: Soyut Türler ve Jenerikler


Yanıtlar:


257

Burada bu konuda iyi bir görüşünüz var:

Scala'nın Tip
A Sistemi'nin Amacı Martin Odersky ile Konuşması, Bölüm III
, Bill Venners ve Frank Sommers (18 Mayıs 2009)

Güncelleme (Ekim2009): Bill Venners'ın bu yeni makalesinde aşağıdakiler gerçekte gösterilmiştir:
Özet Tip Üyeler ve Scala'daki Genel Tip Parametreleri (sondaki özete bakın)


(İşte ilk görüşme, Mayıs 2009, vurgu mayın ilgili özü)

Genel prensip

Her zaman iki soyutlama kavramı olmuştur:

  • parametreleştirme ve
  • soyut üyeler.

Java'da da her ikisine de sahipsiniz, ancak ne yaptığınıza bağlıdır.
Java'da soyut yöntemleriniz vardır, ancak bir yöntemi parametre olarak geçiremezsiniz.
Soyut alanlarınız yok, ancak parametre olarak bir değer iletebilirsiniz.
Benzer şekilde soyut tür üyeleriniz de yoktur, ancak parametre olarak bir tür belirtebilirsiniz.
Yani Java'da bunların üçüne de sahipsiniz, ancak ne tür şeyler için hangi soyutlama ilkesini kullanabileceğiniz konusunda bir ayrım var. Ve bu ayrımın oldukça keyfi olduğunu iddia edebilirsiniz.

Scala Yolu

Her üç üye için de aynı yapım ilkelerine sahip olmaya karar verdik .
Böylece değer alanlarının yanı sıra soyut alanlara da sahip olabilirsiniz.
Yöntemleri (veya "fonksiyonları") parametre olarak iletebilir veya bunların üzerine soyutlayabilirsiniz.
Türleri parametre olarak belirtebilir veya üzerlerine soyut yazabilirsiniz.
Kavramsal olarak elde ettiğimiz şey, birini diğerine göre modelleyebilmemizdir. En azından prensip olarak, her türlü parametrelendirmeyi nesne yönelimli bir soyutlama biçimi olarak ifade edebiliriz. Yani bir anlamda Scala'nın daha dik ve tam bir dil olduğunu söyleyebilirsiniz.

Neden?

Özellikle soyut türlerin sizi satın aldığı şey, daha önce bahsettiğimiz bu kovaryans problemleri için güzel bir tedavidir .
Uzun zamandır var olan standart bir sorun, hayvan ve gıda sorunudur.
Bulmaca, biraz yemek yiyen Animalbir yöntemle bir sınıfa sahip eatolmaktı.
Sorun şu ki, eğer Hayvan'ı alt sınıfa ayırırsak ve İnek gibi bir sınıfa sahip olursak, o zaman sadece ot yiyip keyfi yiyecekler yiyemezlerdi. Örneğin bir İnek Balık yiyemedi.
İstediğiniz şey, bir İnek'in başka şeyleri değil, sadece Çim yiyen bir yemek yöntemine sahip olduğunu söyleyebilmektir.
Aslında bunu Java'da yapamazsınız, çünkü daha önce bahsettiğim bir Apple değişkenine Meyve atama problemi gibi sağlıksız durumlar oluşturabilirsiniz.

Cevap, Animal sınıfına soyut bir tür eklemenizdir .
Diyorsun ki, benim yeni Hayvan sınıfımın SuitableFoodbilmediğim bir türü var .
Yani soyut bir tür. Bu tür bir uygulama vermezsiniz. Sonra eatsadece yiyen bir yöntem var SuitableFood.
Ve sonra Cowderim ki, tamam, sınıfı genişleten bir İnek var Animalve Cow type SuitableFood equals Grass.
Böylece soyut türler, daha sonra alt sınıflarda bildiğim bir şeyle doldurduğum, bilmediğim bir üst sınıftaki bu tür kavramını sağlar .

Parametrelemeyle aynı mı?

Gerçekten de yapabilirsiniz. Animal sınıfını yediği yiyecekle parametrelendirebilirsiniz.
Ancak pratikte, bunu birçok farklı şeyle yaptığınızda, parametrelerin patlamasına ve genellikle dahası, parametre sınırlarında yol açar .
1998 ECOOP'da Kim Bruce, Phil Wadler ve ben, bilmediğiniz şeylerin sayısını artırdıkça, tipik programın karesel olarak büyüyeceğini gösterdiğimiz bir makalemiz vardı .
Bu nedenle, parametreler yapmak değil, bu soyut üyelere sahip olmak için çok iyi nedenler var, çünkü size bu ikinci dereceden patlamayı vermiyorlar.


thatismatt yorumlarda soruyor:

Aşağıdakilerin adil bir özet olduğunu düşünüyor musunuz?

  • Özet Türleri 'has-a' veya 'use-a' ilişkilerinde kullanılır (örneğin a Cow eats Grass)
  • jenerikler genellikle ilişkilerden 'olduğu' (örneğin List of Ints)

Soyut tipler veya jenerikler arasındaki ilişkinin bu kadar farklı olduğundan emin değilim. Farklı olan:

  • nasıl kullanıldıklarını ve
  • parametre sınırları nasıl yönetilir.

"Parametrelerin patlaması ve genellikle daha fazlası, parametrelerin sınırları içinde " söz konusu olduğunda Martin'in neden bahsettiğini ve soyut tip jenerikler kullanılarak modellenirken sonraki kuadratik olarak büyümesini anlamak için , " Ölçeklenebilir Bileşen Soyutlama " adlı makaleyi düşünebilirsiniz. "Yazan ... Martin Odersky ve OOPSLA 2005 için Matthias Zenger, Palcom (2007'de tamamlandı) projesinin yayınlarında referans aldı .

İlgili ekstreler

Tanım

Soyut tip üyeler , somut bileşen türleri üzerinde soyutlama için esnek bir yol sağlar.
Soyut türler, bir bileşenin iç öğeleri hakkındaki bilgileri, SML imzalarındaki kullanımlarına benzer şekilde gizleyebilir . Sınıfların kalıtım yoluyla genişletilebildiği nesne yönelimli bir çerçevede, bunlar esnek bir parametrelendirme aracı olarak da kullanılabilir (genellikle aile polimorfizmi olarak adlandırılır, örneğin bu weblog girişine ve Eric Ernst tarafından yazılmış makaleye bakın ).

(Not:. Aile polimorfizmi yeniden kullanılabilir henüz tip-güvenli karşılıklı özyinelemeli sınıfları destekleyen bir çözüm olarak nesne yönelimli diller için önerilmiştir
aile polimorfizm kilit düşünce grubu karşılıklı olarak özyinelemeli sınıfları için kullanılan ailelerin kavramı vardır)

sınırlı tip soyutlama

abstract class MaxCell extends AbsCell {
type T <: Ordered { type O = T }
def setMax(x: T) = if (get < x) set(x)
}

Burada, T'nin tür bildirimi, Sıralı bir sınıf adı ve bir ayrıntılandırmadan oluşan bir üst tür sınırıyla sınırlıdır { type O = T }.
Üst sınır, alt sınıflardaki T uzmanlıklarını, tip üyesinin üyesi olduğu Ordered alt tipleriyle Osınırlar equals T.
Bu kısıtlamadan dolayı, <Ordered sınıfının yönteminin bir alıcıya ve T tipi bir argümana uygulanacağı garanti edilir
. Örnek, sınırlı tip elemanının kendisinin sınırın bir parçası olarak görünebileceğini göstermektedir.
(yani Scala, F-sınırlı polimorfizmi destekler )

(Not: Peter Canning, William Cook, Walter Hill, Walter Olthoff makalesi:
Sınırlı nicelendirme, belirli bir türdeki tüm alt tipler üzerinde düzgün çalışan işlevler yazmak için Cardelli ve Wegner tarafından tanıtıldı.
Basit bir "nesne" modeli tanımladılar . "nitelikler". belirli bir kümesi olan tüm nesneler üzerinde mantıklı olduğunu ve tip-onay için işlevler sınırlı kantifikasyonunu kullanılan
elemanları nesneleri izin verecek nesne yönelimli dillerin daha gerçekçi sunum yinelemeli tanımlı tipler .
Bu bağlamda, sınırlanmış Ölçme artık amaçlanan amaca hizmet etmemektedir Belirli bir yöntem kümesine sahip olan, ancak Cardelli-Wegner sistemine yazılamayan tüm nesneler için anlamlı olan fonksiyonlar bulmak kolaydır.
Nesneye yönelik dillerde yazılan polimorfik fonksiyonlar için bir temel sağlamak için F-sınırlı nicelemeyi sunuyoruz)

Aynı madeni paraların iki yüzü

Programlama dillerinde iki temel soyutlama şekli vardır:

  • parametreleştirme ve
  • soyut üyeler.

Birinci form işlevsel diller için tipikken, ikinci form tipik olarak nesne yönelimli dillerde kullanılır.

Geleneksel olarak Java, değerler için parametreleştirmeyi ve işlemler için üye soyutlamayı destekler. Jenerikli daha yeni Java 5.0, türler için de parametrelendirmeyi destekler.

Scala'ya jenerik ilaçları dahil etme argümanları iki yönlüdür:

  • Birincisi, soyut türlere kodlama elle yapılması kolay değildir. Kısalıktaki kaybın yanı sıra, tür parametrelerini taklit eden soyut tür adları arasında yanlışlıkla ad çakışmaları sorunu da vardır.

  • İkincisi, jenerikler ve soyut türler genellikle Scala programlarında farklı roller üstlenir.

    • Jenerikler tipik olarak sadece tür instantiasyona ihtiyaç duyulduğunda kullanılır , oysa
    • özet türleri genellikle istemci kodundan özet türüne başvurulması gerektiğinde kullanılır .
      İkincisi özellikle iki durumda ortaya çıkar:
    • SML tarzı modül sistemlerinden bilinen bir tür kapsülleme elde etmek için bir tür üyenin tam tanımını istemci kodundan gizlemek isteyebilirsiniz.
    • Ya da aile polimorfizmi elde etmek için alt sınıflarda türü kovaryant olarak geçersiz kılmak isteyebilirsiniz.

Sınırlı polimorfizmi olan bir sistemde, soyut tipin jeneriklere yeniden yazılması tip sınırlarının ikinci dereceden genişlemesini gerektirebilir .


Ekim 2009 Güncellemesi

Özet Tip Üyeleri ve Scala'daki Genel Tip Parametrelerine Karşı (Bill Venners)

(benimkini vurgula)

Şimdiye kadar soyut tip üyeler hakkındaki gözlemim , aşağıdaki durumlarda genellikle genel tip parametrelerinden daha iyi bir seçim olmalarıdır:

  • insanların bu tür tanımlara özellikler yoluyla karışmasına izin vermek istersiniz .
  • Sence bu irade yardım kod okunabilirliği tanımlanan ediliyor tipi üyesi adının açıkça bahsetmektedir .

Misal:

testlere üç farklı fikstür nesnesini geçirmek istiyorsanız, bunu yapabilirsiniz, ancak her parametre için bir tane olmak üzere üç tür belirtmeniz gerekir. Böylece tip parametresi yaklaşımını almış olsaydım, süit sınıflarınız şöyle görünebilirdi:

// Type parameter version
class MySuite extends FixtureSuite3[StringBuilder, ListBuffer, Stack] with MyHandyFixture {
  // ...
}

Oysa tür üyesi yaklaşımıyla şöyle görünecektir:

// Type member version
class MySuite extends FixtureSuite3 with MyHandyFixture {
  // ...
}

Soyut tür üyeleri ile genel tür parametreleri arasındaki diğer bir küçük fark, genel tür parametresi belirtildiğinde, kod okuyucularının tür parametresinin adını görmemeleridir. Böylece birisi bu kod satırını görecekti:

// Type parameter version
class MySuite extends FixtureSuite[StringBuilder] with StringBuilderFixture {
  // ...
}

StringBuilder olarak belirtilen type parametresinin adının bakmadan ne olduğunu bilmiyorlardı. Type parametresinin adı, soyut tip üye yaklaşımındaki kodda bulunurken:

// Type member version
class MySuite extends FixtureSuite with StringBuilderFixture {
  type FixtureParam = StringBuilder
  // ...
}

İkinci durumda, kod okuyucuları StringBuilder"fikstür parametresi" türünü görebilir.
Yine de "fikstür parametresi" nin ne anlama geldiğini anlamaları gerekir, ancak en azından belgelere bakmadan türün adını alabilirler.


61
Gelip bunu yaptığınızda Scala sorularını cevaplayarak karma puanları nasıl alabilirim? :-)
Daniel C. Sobral

7
Merhaba Daniel: Bence soyut türlerin parametrelendirmeye göre avantajlarını göstermek için somut örnekler olmalı. Bu konuda bazı mesaj göndermek iyi bir başlangıç ​​olurdu;) Bunu upvote olacağını biliyorum.
VonC

1
Aşağıdakilerin adil bir özet olduğunu düşünüyor musunuz? Özet Türleri, jenerikler genellikle 'ilişki' olduğu için (örneğin, Ints Listesi) olduğu gibi 'has-a' veya 'use-a' ilişkilerinde (örn. İnek Çim yer) kullanılır
thatismatt

Soyut tipler ile jenerikler arasındaki ilişkinin bu kadar farklı olduğundan emin değilim. Farklı olan, bunların nasıl kullanıldığı ve parametre sınırlarının nasıl yönetildiği. Bir anda cevabımda daha fazlası var.
VonC


37

Scala hakkında okurken aynı soruyu sordum.

Jenerik ilaç kullanmanın avantajı, bir tür aile yaratmanızdır. Kimse alt sınıfı gerekecektir Buffer-onlar sadece kullanabilirsiniz Buffer[Any], Buffer[String]vb

Soyut bir tür kullanırsanız, insanlar bir alt sınıf oluşturmak zorunda kalacaktır. İnsanlar gibi sınıflar gerekecektir AnyBuffer, StringBuffervs.

Özel ihtiyaçlarınız için hangisinin daha iyi olduğuna karar vermelisiniz.


18
mmm thins bu cephede çok gelişti, sadece ihtiyaç duyduğunuz Buffer { type T <: String }veya Buffer { type T = String }ihtiyaçlarınıza bağlı olarak
Eduardo Pareja Tobes

21

Özel şablonlar oluşturmak için soyut türleri tip parametreleriyle birlikte kullanabilirsiniz.

Üç bağlı özelliğe sahip bir model oluşturmanız gerektiğini varsayalım:

trait AA[B,C]
trait BB[C,A]
trait CC[A,B]

tür parametrelerinde belirtilen argümanların AA, BB, CC'nin saygılı olması

Bir çeşit kodla gelebilirsiniz:

trait AA[B<:BB[C,AA[B,C]],C<:CC[AA[B,C],B]]
trait BB[C<:CC[A,BB[C,A]],A<:AA[BB[C,A],C]]
trait CC[A<:AA[B,CC[A,B]],B<:BB[CC[A,B],A]]

bu tür parametre bağları nedeniyle bu basit şekilde çalışmaz. Doğru miras almayı kovaryant yapmanız gerekir

trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]]
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]]
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]]

Bu bir örnek derlenir, ancak varyans kuralları için güçlü gereksinimler belirler ve bazı durumlarda kullanılamaz

trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]] {
  def forth(x:B):C
  def back(x:C):B
}
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]] {
  def forth(x:C):A
  def back(x:A):C
}
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]] {
  def forth(x:A):B
  def back(x:B):A
}

Derleyici, bir grup varyans kontrolü hatası ile itiraz eder

Bu durumda, tüm özellik gereksinimlerini ek özellikte toplayabilir ve diğer özellikleri parametrelendirebilirsiniz.

//one trait to rule them all
trait OO[O <: OO[O]] { this : O =>
  type A <: AA[O]
  type B <: BB[O]
  type C <: CC[O]
}
trait AA[O <: OO[O]] { this : O#A =>
  type A = O#A
  type B = O#B
  type C = O#C
  def left(l:B):C
  def right(r:C):B = r.left(this)
  def join(l:B, r:C):A
  def double(l:B, r:C):A = this.join( l.join(r,this), r.join(this,l) )
}
trait BB[O <: OO[O]] { this : O#B =>
  type A = O#A
  type B = O#B
  type C = O#C
  def left(l:C):A
  def right(r:A):C = r.left(this)
  def join(l:C, r:A):B
  def double(l:C, r:A):B = this.join( l.join(r,this), r.join(this,l) )
}
trait CC[O <: OO[O]] { this : O#C =>
  type A = O#A
  type B = O#B
  type C = O#C
  def left(l:A):B
  def right(r:B):A = r.left(this)
  def join(l:A, r:B):C
  def double(l:A, r:B):C = this.join( l.join(r,this), r.join(this,l) )
}

Şimdi tarif edilen model için somut bir gösterim yazabilir, tüm sınıflarda sola ve birleştirme yöntemlerini tanımlayabilir ve ücretsiz olarak sağ ve iki katına çıkabiliriz

class ReprO extends OO[ReprO] {
  override type A = ReprA
  override type B = ReprB
  override type C = ReprC
}
case class ReprA(data : Int) extends AA[ReprO] {
  override def left(l:B):C = ReprC(data - l.data)
  override def join(l:B, r:C) = ReprA(l.data + r.data)
}
case class ReprB(data : Int) extends BB[ReprO] {
  override def left(l:C):A = ReprA(data - l.data)
  override def join(l:C, r:A):B = ReprB(l.data + r.data)
}
case class ReprC(data : Int) extends CC[ReprO] {
  override def left(l:A):B = ReprB(data - l.data)
  override def join(l:A, r:B):C = ReprC(l.data + r.data)
}

Bu nedenle, hem soyut tipler hem de tip parametreleri soyutlamalar oluşturmak için kullanılır. İkisinin de zayıf ve güçlü bir yanı var. Soyut türler daha spesifiktir ve herhangi bir tür yapısını tanımlayabilir, ancak ayrıntılıdır ve açıkça belirtilmesi gerekir. Tür parametreleri anında türler oluşturabilir, ancak kalıtım ve tür sınırları hakkında ek endişe verir.

Birbirlerine sinerji verirler ve bunlardan sadece biriyle ifade edilemeyen karmaşık soyutlamalar yaratmak için birlikte kullanılabilirler.


0

Bence burada çok fazla fark yok. Tür soyut üyeleri, diğer bazı işlevsel dillerde kayıt türlerine benzeyen varoluşsal türler olarak görülebilir.

Örneğin:

class ListT {
  type T
  ...
}

ve

class List[T] {...}

O zaman ListTsadece aynı List[_]. Tip üyelerinin uygunluğu, sınıfı açık beton tipi olmadan kullanabilmemiz ve çok fazla tip parametresinden kaçınabilmemizdir.

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.