Scala tipi programlama kaynakları


102

Bu soruya göre Scala'nın tip sistemi Turing tamamlandı . Yeni gelenlerin tür düzeyinde programlamanın gücünden yararlanmasını sağlayan hangi kaynaklar mevcuttur?

Şimdiye kadar bulduğum kaynaklar şunlardır:

Bu kaynaklar harika, ancak temelleri kaçırdığımı hissediyorum ve bu nedenle üzerine inşa edeceğim sağlam bir temele sahip değilim. Örneğin, tür tanımlarına bir giriş nerede var? Türler üzerinde hangi işlemleri gerçekleştirebilirim?

İyi bir giriş kaynağı var mı?


Kişisel olarak, Scala'da tür düzeyinde programlama yapmak isteyen birinin Scala'da programlama yapmayı zaten bildiği varsayımını oldukça makul buluyorum. :-)
Jörg W Mittag

Yanıtlar:


140

Genel Bakış

Tür düzeyinde programlamanın geleneksel, değer düzeyinde programlamayla birçok benzerliği vardır. Ancak, hesaplamanın çalışma zamanında gerçekleştiği değer düzeyinde programlamanın aksine, tür düzeyinde programlamada hesaplama derleme zamanında gerçekleşir. Değer düzeyinde programlama ile tür düzeyinde programlama arasında paralellikler kurmaya çalışacağım.

Paradigmalar

Tür düzeyinde programlamada iki ana paradigma vardır: "nesne yönelimli" ve "işlevsel". Buradan bağlantılı örneklerin çoğu, nesne yönelimli paradigmayı takip eder.

Nesne yönelimli paradigmadaki tür düzeyinde programlamanın iyi, oldukça basit bir örneği, apocalisp'in lambda hesabının uygulamasında bulunabilir , burada çoğaltılır:

// Abstract trait
trait Lambda {
  type subst[U <: Lambda] <: Lambda
  type apply[U <: Lambda] <: Lambda
  type eval <: Lambda
}

// Implementations
trait App[S <: Lambda, T <: Lambda] extends Lambda {
  type subst[U <: Lambda] = App[S#subst[U], T#subst[U]]
  type apply[U] = Nothing
  type eval = S#eval#apply[T]
}

trait Lam[T <: Lambda] extends Lambda {
  type subst[U <: Lambda] = Lam[T]
  type apply[U <: Lambda] = T#subst[U]#eval
  type eval = Lam[T]
}

trait X extends Lambda {
  type subst[U <: Lambda] = U
  type apply[U] = Lambda
  type eval = X
}

Örnekte görülebileceği gibi, tür düzeyinde programlama için nesne yönelimli paradigma şu şekilde ilerler:

  • İlk olarak: çeşitli soyut tip alanlarla soyut bir özellik tanımlayın (soyut alanın ne olduğunu görmek için aşağıya bakın). Bu, bir uygulamayı zorlamadan tüm uygulamalarda belirli tür alanların var olduğunu garanti eden bir şablondur. Lambda taşı örnekte, bu karşılık trait Lambdabu garantiler aşağıdaki türleri mevcut olduğunu: subst, applyve eval.
  • Ardından: soyut özelliği genişleten ve çeşitli soyut tür alanlarını uygulayan alt çizgiler tanımlayın
    • Genellikle, bu alt çizgiler bağımsız değişkenlerle parametreleştirilir. Lambda hesabı örneğinde, iki türle trait App extends Lambdaparametreleştirilen ( Sve Ther ikisi de alt türleri olmalıdır Lambda), bir türle ( ) trait Lam extends Lambdaparametreleştirilmiş Tve trait X extends Lambda(parametreleştirilmemiş) alt türlerdir .
    • tür alanları genellikle alt yüzeyin tür parametrelerine atıfta bulunularak ve bazen hash operatörü #(nokta operatörüne çok benzer: .değerler için) aracılığıyla tür alanlarına başvurarak uygulanır . Özellik olarak Applambda taşı örneğin, türü eval, aşağıdaki gibi uygulanır: type eval = S#eval#apply[T]. Bu aslında evalözelliğin parametresinin türünü ve sonuçta parametre ile Sçağırmaktır . Not, bir türe sahip olma garantilidir çünkü parametre bunun bir alt türü olduğunu belirtir . Benzer şekilde, sonucu , soyut özellikte belirtildiği gibi, bir alt türü olarak belirtildiğinden, bir türe sahip olmalıdır .applyTSevalLambdaevalapplyLambdaLambda

İşlevsel paradigma, özelliklerde birlikte gruplandırılmamış çok sayıda parametreleştirilmiş tür kurucusunun tanımlanmasından oluşur.

Değer düzeyinde programlama ve tür düzeyinde programlama arasında karşılaştırma

  • soyut sınıf
    • değer düzeyi: abstract class C { val x }
    • tür düzeyi: trait C { type X }
  • yola bağlı türler
    • C.x (C nesnesindeki alan değeri / işlevi x'e başvurma)
    • C#x (C özelliğinde alan türü x referans alınarak)
  • işlev imzası (uygulama yok)
    • değer düzeyi: def f(x:X) : Y
    • tür düzeyi: type f[x <: X] <: Y(buna "tür oluşturucu" denir ve genellikle soyut özellikte görülür)
  • işlev uygulaması
    • değer düzeyi: def f(x:X) : Y = x
    • tür düzeyi: type f[x <: X] = x
  • şartlılar
  • eşitliği kontrol etmek
    • değer düzeyi: a:A == b:B
    • tür düzeyi: implicitly[A =:= B]
    • değer düzeyi: Çalışma zamanında bir birim testi aracılığıyla JVM'de gerçekleşir (yani çalışma zamanı hatası yoktur):
      • özünde bir iddiadır: assert(a == b)
    • tür düzeyi: Bir yazım denetimi aracılığıyla derleyicide gerçekleşir (yani derleyici hatası yoktur):
      • özünde bir tür karşılaştırmasıdır: örneğin implicitly[A =:= B]
      • A <:< B, yalnızca Abir alt türü ise derlerB
      • A =:= B, Yalnızca derler Abir alt tipi olan Bve Bbir alt tipi olanA
      • A <%< B, ("görüntülenebilir") yalnızca Aşu şekilde görüntülenebilirse derlenir B(yani Aöğesinden alt türüne örtük bir dönüşüm varsa B)
      • Bir örnek
      • daha fazla karşılaştırma operatörü

Türler ve değerler arasında dönüştürme

  • Örneklerin çoğunda, özellikler aracılığıyla tanımlanan türler genellikle hem soyuttur hem de mühürlenir ve bu nedenle ne doğrudan ne de anonim alt sınıf aracılığıyla somutlaştırılabilir. Bu nedenle, nullbir tür ilgi alanı kullanarak değer düzeyinde hesaplama yaparken yer tutucu değer olarak kullanılması yaygındır :

    • ör. önemsediğiniz tür val x:A = nullneredeA
  • Tür silme nedeniyle, parametreli türlerin tümü aynı görünür. Ayrıca, (yukarıda belirtildiği gibi) çalıştığınız değerlerin tümü olma eğilimindedir nullve bu nedenle nesne türünde koşullandırma (örneğin bir eşleşme ifadesi aracılığıyla) etkisizdir.

İşin püf noktası, örtük işlevleri ve değerleri kullanmaktır. Temel durum genellikle örtük bir değerdir ve özyinelemeli durum genellikle örtük bir işlevdir. Nitekim, tür düzeyinde programlama, sonuçların yoğun şekilde kullanılmasını sağlar.

Şu örneği düşünün ( metascala ve apocalisp'ten alınmıştır ):

sealed trait Nat
sealed trait _0 extends Nat
sealed trait Succ[N <: Nat] extends Nat

Burada doğal sayıların bir peano kodlaması var. Yani, negatif olmayan her tam sayı için bir türünüz vardır: 0 için özel bir tür, yani _0; ve sıfırdan her tam sayı formunun bir türü olan Succ[A], Adaha küçük bir tam sayıyı temsil türüdür. Örneğin, 2'yi temsil eden tür: Succ[Succ[_0]](sıfırı temsil eden türe iki kez uygulanmış halef).

Daha uygun referans için çeşitli doğal sayıları takma ad verebiliriz. Misal:

type _3 = Succ[Succ[Succ[_0]]]

(Bu, a'yı valbir işlevin sonucu olarak tanımlamaya çok benzer .)

Şimdi, def toInt[T <: Nat](v : T)bir argüman değerini alan, türünde kodlanmış doğal sayıyı temsil eden bir tamsayıya vuyan Natve onu döndüren bir değer düzeyinde işlev tanımlamak istediğimizi varsayalım v. Örneğin, eğer val x:_3 = null( nulltürün Succ[Succ[Succ[_0]]]) değerine sahipsek toInt(x), geri dönmek isteriz 3.

Uygulamak toIntiçin aşağıdaki sınıftan yararlanacağız:

class TypeToValue[T, VT](value : VT) { def getValue() = value }

Aşağıda görüleceği gibi, orada sınıfından yapılmış bir nesne olacak TypeToValueher Natgelen _0(örneğin) 'ye kadar _3, ve karşılık gelen her bir tip (yani değeri temsil depolayacak TypeToValue[_0, Int]değeri depolayacaktır 0, TypeToValue[Succ[_0], Int]değeri depolayacaktır 1, vs.). Not, TypeToValueiki türle parametrelendirilir: Tve VT. Tdeğerler atamaya çalıştığımız türe karşılık gelir (örneğimizde Nat) ve VTona atadığımız değerin türüne karşılık gelir (örneğimizde Int).

Şimdi aşağıdaki iki örtük tanımı yapıyoruz:

implicit val _0ToInt = new TypeToValue[_0, Int](0)
implicit def succToInt[P <: Nat](implicit v : TypeToValue[P, Int]) = 
     new TypeToValue[Succ[P], Int](1 + v.getValue())

Ve toIntaşağıdaki gibi uyguluyoruz :

def toInt[T <: Nat](v : T)(implicit ttv : TypeToValue[T, Int]) : Int = ttv.getValue()

Nasıl toIntçalıştığını anlamak için birkaç girdi üzerinde ne yaptığını düşünelim:

val z:_0 = null
val y:Succ[_0] = null

Çağırdığımızda toInt(z), derleyici örtük bir ttvtür argümanı arar TypeToValue[_0, Int](çünkü ztürden olduğu için _0). Nesneyi bulur, bu nesnenin yöntemini _0ToIntçağırır getValueve geri döner 0. Unutulmaması gereken önemli nokta, programa hangi nesnenin kullanılacağını belirtmediğimizdir, derleyicinin onu örtük olarak bulmasıdır.

Şimdi bir düşünelim toInt(y). Bu sefer, derleyici ttvtürden örtük bir argüman arar TypeToValue[Succ[_0], Int](çünkü ytürden Succ[_0]). succToIntUygun tipte ( TypeToValue[Succ[_0], Int]) bir nesne döndürebilen işlevi bulur ve değerlendirir. Bu işlevin kendisi v, türden örtük bir argüman ( ) alır TypeToValue[_0, Int](yani, TypeToValuebirinci tür parametresinin daha az olduğu yerde Succ[_]). Derleyici _0ToInt( toInt(z)yukarıdaki değerlendirmede yapıldığı gibi) sağlar ve değeri succToIntolan yeni bir TypeToValuenesne oluşturur 1. Yine, derleyicinin tüm bu değerleri örtük olarak sağladığına dikkat etmek önemlidir, çünkü bunlara açıkça erişemiyoruz.

İşini kontrol ediyorum

Tür düzeyindeki hesaplamalarınızın beklediğiniz şeyi yaptığını doğrulamanın birkaç yolu vardır. İşte birkaç yaklaşım. Doğrulamak istediğiniz iki tür yapın Ave Beşittir. Ardından aşağıdaki derlemenin yapıldığını kontrol edin:

Alternatif olarak, türü bir değere dönüştürebilir (yukarıda gösterildiği gibi) ve değerlerin çalışma zamanı kontrolünü yapabilirsiniz. Örneğin assert(toInt(a) == toInt(b)), nerede atür Ave btürdür B.

Ek kaynaklar

Mevcut yapıların tam seti , ölçek referans kılavuzunun (pdf) tipler bölümünde bulunabilir .

Adriaan Moors , tip oluşturucular ve ilgili konular hakkında çeşitli akademik makalelere sahiptir ve bunlarla ilgili örneklerden örnekler:

Apocalisp , birçok tür düzeyinde programlama örneğinin ölçeklendirildiği bir blogdur .

ScalaZ , çeşitli tip düzeyinde programlama özelliklerini kullanarak Scala API'yi genişleten işlevsellik sağlayan çok aktif bir projedir. Büyük bir takipçi kitlesi olan çok ilginç bir proje.

MetaScala , doğal sayılar, boole'lar, birimler, HList, vb. İçin meta türleri içeren Scala için tür düzeyinde bir kitaplıktır. Bu, Jesper Nordenberg'in (blogu) bir projesidir .

The Michid (blog) , Scala'da tür düzeyinde programlamanın bazı harika örneklerine sahiptir (diğer yanıttan):

Debasish Ghosh'un (blog) bazı ilgili yayınları da var:

(Bu konuda biraz araştırma yapıyorum ve işte öğrendiklerim. Hala yeniyim, bu yüzden lütfen bu cevaptaki herhangi bir yanlışlığı işaret edin.)


12

Sadece ilginç blog için teşekkür etmek istedim; Bunu bir süredir takip ediyorum ve özellikle yukarıda bahsedilen son gönderi, nesneye yönelik bir dil için bir tür sisteminin sahip olması gereken önemli özellikler hakkındaki anlayışımı keskinleştirdi. Bu yüzden teşekkürler!
Zach Snow



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.