Bağımlı yöntem türleri için bazı zorlayıcı kullanım durumları nelerdir?


127

Daha önce deneysel bir özellik olarak kullanılan bağımlı yöntem türleri, artık ana hatta varsayılan olarak etkinleştirildi ve görünüşe göre bu biraz heyecan yaratmış gibi görünüyor , Scala topluluğunda .

İlk bakışta, bunun ne için yararlı olabileceği hemen belli değil. Heiko Seeberger, bağımlı yöntem türlerinin basit bir örneğini burada yayınladı ; yorumda görülebileceği gibi, yöntemlerde tür parametreleri ile kolayca yeniden üretilebilir. Yani bu pek ikna edici bir örnek değildi. (Bariz bir şeyi kaçırıyor olabilirim. Varsa lütfen beni düzeltin.)

Alternatiflere göre açıkça avantajlı oldukları bağımlı yöntem türleri için kullanım durumlarının bazı pratik ve faydalı örnekleri nelerdir?

Onlarla daha önce mümkün olmayan / kolay olmayan ne gibi ilginç şeyler yapabiliriz?

Mevcut tip sistem özellikleri üzerinden bize ne alıyorlar?

Ayrıca, bağımlı yöntem türleri Haskell, OCaml gibi diğer gelişmiş türdeki dillerin tür sistemlerinde bulunan özelliklerden ilham alıyor mu veya bunlardan ilham alıyor mu?


Sen incelerken ilginizi çekebilir haskell.org/haskellwiki/Dependent_type
Dan Burton

Bağlantı için teşekkürler Dan! Genel olarak bağımlı türlerin farkındayım, ancak bağımlı yöntem türleri kavramı benim için nispeten yeni.
missingfaktor

Bana öyle geliyor ki "bağımlı yöntem türleri", bir veya daha fazla yöntemin girdi türüne (yöntemin çağrıldığı nesnenin türü dahil) bağımlı olan türlerdir; bağımlı tipler genel fikrinin ötesinde çılgınca bir şey yok. Belki bir şeyi kaçırıyorum?
Dan Burton

Hayır, yapmadın, ama görünüşe göre yaptım. :-) Daha önce ikisi arasındaki bağlantıyı görmedim. Gerçi şimdi çok net.
missingfaktor

Yanıtlar:


112

Üye (yani, iç içe geçmiş) türlerin az çok herhangi bir kullanımı, bağımlı yöntem türlerine ihtiyaç doğurabilir. Özellikle, bağımlı yöntem türleri olmadan klasik pasta modelinin bir anti-model olmaya daha yakın olduğunu düşünüyorum.

Peki sorun nedir? Scala'daki yuvalanmış türler, kapsayıcı örneklerine bağlıdır. Sonuç olarak, bağımlı yöntem türlerinin yokluğunda, bunları bu örneğin dışında kullanma girişimleri sinir bozucu şekilde zor olabilir. Bu, başlangıçta zarif ve çekici görünen tasarımları kabus gibi sert ve yeniden düzenlemesi zor canavarlara dönüştürebilir.

Benim sırasında vermek bir egzersiz ile bu açıklayacağız Gelişmiş Scala eğitim kursu ,

trait ResourceManager {
  type Resource <: BasicResource
  trait BasicResource {
    def hash : String
    def duplicates(r : Resource) : Boolean
  }
  def create : Resource

  // Test methods: exercise is to move them outside ResourceManager
  def testHash(r : Resource) = assert(r.hash == "9e47088d")  
  def testDuplicates(r : Resource) = assert(r.duplicates(r))
}

trait FileManager extends ResourceManager {
  type Resource <: File
  trait File extends BasicResource {
    def local : Boolean
  }
  override def create : Resource
}

class NetworkFileManager extends FileManager {
  type Resource = RemoteFile
  class RemoteFile extends File {
    def local = false
    def hash = "9e47088d"
    def duplicates(r : Resource) = (local == r.local) && (hash == r.hash)
  }
  override def create : Resource = new RemoteFile
}

Bu, klasik pasta modelinin bir örneğidir: bir hiyerarşi aracılığıyla kademeli olarak rafine edilen ( ResourceManager/ Resourcetarafından rafine edilen FileManager/ Filedaha sonra NetworkFileManager/ tarafından rafine edilen RemoteFile) bir soyutlama ailesine sahibiz . Bu bir oyuncak örneği, ancak desen gerçektir: Scala derleyicisinin tamamında kullanıldı ve Scala Eclipse eklentisinde yoğun bir şekilde kullanıldı.

İşte kullanımdaki soyutlamanın bir örneği:

val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)

Yol bağımlılığının, derleyicinin testHashve testDuplicatesyöntemlerinin NetworkFileManageryalnızca ona karşılık gelen argümanlarla çağrılabileceğini garanti edeceği anlamına geldiğine dikkat edin . kendi RemoteFilesve başka bir şey değil.

Bu inkar edilemez bir şekilde arzu edilen bir özellik, ancak bu test kodunu farklı bir kaynak dosyaya taşımak istediğimizi varsayalım. Bağımlı yöntem türleri ile bu yöntemleri ResourceManagerhiyerarşinin dışında yeniden tanımlamak oldukça kolaydır ,

def testHash4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.hash == "9e47088d")

def testDuplicates4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.duplicates(r))

Burada bağımlı yöntem türlerinin kullanımlarına dikkat edin: ikinci argümanın ( rm.Resource) türü ilk argümanın ( rm) değerine bağlıdır .

Bağımlı yöntem türleri olmadan bunu yapmak mümkündür, ancak bu son derece garip ve mekanizma oldukça mantıksız: Bu kursu yaklaşık iki yıldır öğretiyorum ve bu süre zarfında kimse istenmeden çalışan bir çözüm bulamadı.

Kendiniz deneyin ...

// Reimplement the testHash and testDuplicates methods outside
// the ResourceManager hierarchy without using dependent method types
def testHash        // TODO ... 
def testDuplicates  // TODO ...

testHash(rf)
testDuplicates(rf)

Kısa bir süre onunla mücadele ettikten sonra muhtemelen neden benim (veya belki de David MacIver idi, hangimizin bu terimi icat ettiğini hatırlayamıyoruz) buna Doom Fırını diyoruz.

Düzenleme: Fikir birliği, Bakery of Doom'un David MacIver'ın madeni parası olduğu yönünde ...

Bonus için: Scala'nın genel olarak bağımlı türler biçimi (ve bunun bir parçası olarak bağımlı yöntem türleri) programlama dili Beta'dan esinlenmiştir ... Doğal olarak Beta'nın tutarlı yuvalama anlamından doğarlar. Bu biçimde bağımlı türlere sahip olan, hatta biraz da yaygın olan başka bir programlama dili bilmiyorum. Coq, Cayenne, Epigram ve Agda gibi diller, bazı yönlerden daha genel olan, ancak Scala'dan farklı olarak alt tipleme içermeyen tip sistemlerin parçası olarak önemli ölçüde farklılık gösteren farklı bir bağımlı yazım biçimine sahiptir.


2
Terimi icat eden David MacIver'dı, ancak her durumda oldukça açıklayıcı. Bu, bağımlı yöntem türlerinin neden bu kadar heyecan verici olduğunun harika bir açıklamasıdır. İyi iş!
Daniel Spiewak

İlk önce ikimiz arasında #scala'da yaptığımız konuşmada ortaya çıktı epey bir zaman önce ... Hangimizin ilk söylediğini hatırlayamadığımı söylediğim gibi.
Miles Sabin

Görünüşe göre hafızam bana oyun oynuyor ... fikir birliği David MacIver'ın parasıydı.
Miles Sabin

Evet, o sırada orada değildim (#scala'da), ama Jorge oradaydı ve bilgilerimi oradan alıyordum.
Daniel Spiewak

Soyut tip üye ayrıntılandırmasını kullanarak testHash4 işlevini oldukça acısız bir şekilde uygulayabildim. def testHash4[R <: ResourceManager#BasicResource](rm: ResourceManager { type Resource = R }, r: R) = assert(r.hash == "9e47088d")Sanırım bu, başka bir bağımlı tip türü olarak düşünülebilir.
Marco van Hilst

53
trait Graph {
  type Node
  type Edge
  def end1(e: Edge): Node
  def end2(e: Edge): Node
  def nodes: Set[Node]
  def edges: Set[Edge]
}

Başka bir yerde, iki farklı grafikten düğümleri karıştırmadığımızı statik olarak garanti edebiliriz, örneğin:

def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ... 

Tabii ki, bu içeride tanımlandıysa zaten işe yaradı Graph, ancak değiştiremeyeceğimizi Graphve bunun için bir "kütüphanemi pezevenk" uzantısı yazdığımızı söyle .

İkinci soru hakkında: Bu özelliğin etkinleştirdiği türler , tam bağımlı türlerden çok daha zayıftır (Bunun bir çeşidi için Agda'da Bağımlı Olarak Yazılan Programlama konusuna bakın .) Daha önce bir benzetme gördüğümü sanmıyorum.


6

Bu yeni özellik, tip parametreleri yerine somut soyut tip üyeler kullanıldığında gereklidir . Tip parametreleri kullanıldığında, aile polimorfizm türü bağımlılığı, aşağıdaki basitleştirilmiş örnekte olduğu gibi, Scala'nın en son ve bazı eski sürümlerinde ifade edilebilir.

trait C[A]
def f[M](a: C[M], b: M) = b
class C1 extends C[Int]
class C2 extends C[String]

f(new C1, 0)
res0: Int = 0
f(new C2, "")
res1: java.lang.String = 
f(new C1, "")
error: type mismatch;
 found   : C1
 required: C[Any]
       f(new C1, "")
         ^

Bu alakasız. Tip üyeleriyle aynı sonuç için ayrıntılandırmaları kullanabilirsiniz: trait C {type A}; def f[M](a: C { type A = M}, b: M) = 0;class CI extends C{type A=Int};class CS extends C{type A=String}vb.
nafg

Her durumda bunun bağımlı yöntem türleriyle ilgisi yoktur. Örneğin Alexey örneğini ele alalım ( stackoverflow.com/a/7860821/333643 ). Yaklaşımınızı kullanmak (yorumladığım iyileştirme versiyonu dahil) hedefe ulaşmaz. N1.Node =: = n2.Node olmasını sağlar, ancak ikisinin de aynı grafikte olmasını sağlamaz. IIUC DMT bunu sağlar.
nafg

@nafg Bunu belirttiğiniz için teşekkürler. Tip üyeler için iyileştirme vakasına atıfta bulunmadığımı açıkça belirtmek için beton kelimesini ekledim . Görebildiğim kadarıyla, bu, diğer kullanım durumlarında daha fazla güce sahip olabileceklerine (farkındaydım) rağmen, bağımlı yöntem türleri için hala geçerli bir kullanım durumudur. Yoksa ikinci yorumun özünü özledim mi?
Shelby Moore III

3

Ben ediyorum bir model geliştirilmesi çevre devletle bildirim programlama formunun interoption için. Detaylar burada alakalı değildir (örneğin, geri çağırmalarla ilgili detaylar ve bir Serileştirici ile birleştirilmiş Aktör modeline kavramsal benzerlik).

İlgili sorun, durum değerlerinin bir karma haritada depolanması ve bir karma anahtar değeri ile referans gösterilmesidir. Fonksiyonlar, çevreden gelen değerler olan değişmez argümanlar girer, bu tür diğer fonksiyonları çağırabilir ve durumu ortama yazabilir. Ancak işlevlerin ortamdan değerleri okumasına izin verilmez (bu nedenle işlevin dahili kodu, durum değişikliklerinin sırasına bağlı değildir ve bu nedenle bu anlamda bildirimsel olarak kalır). Bunu Scala'da nasıl yazabilirim?

Ortam sınıfının, böyle bir işlevi çağırmak için ve işlevin argümanlarının karma anahtarlarını giren aşırı yüklenmiş bir yöntemi olmalıdır. Bu nedenle, bu yöntem, değerlere genel okuma erişimi sağlamadan (dolayısıyla, gerektiği gibi, işlevlerin ortamdan değerleri okuma yeteneğini reddederek), hash haritasından gerekli değerlerle işlevi çağırabilir.

Ancak bu karma tuşları dizeleri veya tamsayı karma değerleri karma haritası eleman tipi statik yazarak eğer kapsadığını dolayısıyla (aşağıda gösterilen değil karma harita kodu) herhangi biri veya AnyRef için ve bir çalışma zamanı uyuşmazlığı meydana gelebilir, yani mümkün olacağını belirli bir karma anahtar için bir karma haritaya herhangi bir değer türü koymak için.

trait Env {
...
  def callit[A](func: Env => Any => A, arg1key: String): A
  def callit[A](func: Env => Any => Any => A, arg1key: String, arg2key: String): A
}

Aşağıdakileri test etmemiş olsam da, teorik olarak, hash anahtarlarını çalıştırma sırasında sınıf isimlerinden alabilirim classOf, bu nedenle bir hash anahtarı, bir dize yerine bir sınıf adıdır (Scala'nın geri işaretlerini kullanarak bir sınıf adına bir dize gömmek için).

trait DependentHashKey {
  type ValueType
}
trait `the hash key string` extends DependentHashKey {
  type ValueType <: SomeType
}

Böylece statik tip güvenlik sağlanır.

def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A

Argüman anahtarlarını tek bir değerde geçirmemiz gerektiğinde, test etmedim, ancak bir Tuple kullanabileceğimizi varsayalım, örneğin 2 argüman aşırı yüklemesi için def callit[A](argkeys: Tuple[DependentHashKey,DependentHashKey])(func: Env => argkeys._0.ValueType => argkeys._1.ValueType => A): A. Bir argüman anahtarları koleksiyonu kullanmayız, çünkü öğe türleri koleksiyon türünde yer alır (derleme zamanında bilinmez).
Shelby Moore III

"karma eşleme öğesi türünün statik türü Any veya AnyRef'e dahil edilir" - ben buna uymuyorum. Öğe türü dediğinizde, anahtar türünü veya değer türünü (yani HashMap'e birinci veya ikinci tür bağımsız değişkeni) mi kastediyorsunuz? Ve neden kapsam dahiline alınsın?
Robin Green

@RobinGreen Karma tablodaki değerlerin türü. Afair, Scala'daki bir koleksiyona birden fazla tür koyamayacağınız için, bunların ortak süper tipine dahil etmediğiniz için, Scala'nın bir birleşim (ayrılma) türü olmadığı için dahil edilmiştir. Scala'da dahil etme hakkındaki Soru-Cevap bölümüme bakın.
Shelby Moore III
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.