Bir tür parametresinin tür bağımsız değişkenlerini çıkarım için tekniğin adı?


9

Kurulum: Diyelim Iteratorki bir type parametresi olan bir türümüz var Element:

interface Iterator<Element> {}

Sonra bir Iterabledöndürecek bir yöntemi olan bir arayüz var Iterator.

// T has an upper bound of Iterator
interface Iterable<T: Iterator> {
    getIterator(): T
}

Genel Iteratorolmakla ilgili sorun, ona tür argümanları sunmamız gerektiğidir.

Bunu çözmek için bir fikir, yineleyicinin türünü "çıkarmak" tır. Aşağıdaki sözde kod Element, tür bağımsız değişkeni olduğu düşünülen bir tür değişkeni olduğu fikrini ifade eder Iterator:

interface <Element> Iterable<T: Iterator<Element>> {
    getIterator(): T
}

Ve sonra böyle bir yerde kullanıyoruz:

class Vec<Element> implements Iterable<VecIterator<Element>> {/*...*/}

Bu tanım, tanımında başka hiçbir yerde Iterablekullanmaz Element, ancak gerçek kullanım durumum bunu kullanır. IterableAyrıca kullanılan bazı işlevler Iterable, çift yönlü yineleyici gibi yalnızca belirli yineleyici türlerini döndürenleri kabul etmek için parametrelerini sınırlayabilmelidir ; bu nedenle, döndürülen yineleyici, yalnızca öğe türü yerine parametreleştirilir.


Sorular:

  • Bu çıkarım tipi değişkenler için belirlenmiş bir isim var mı? Bir bütün olarak teknik ne olacak? Belirli bir isimlendirme bilmemek, bu örneklerin vahşi doğada aranmasını veya dile özgü özellikleri öğrenmeyi zorlaştırmıştır.
  • Jenerik olan tüm diller bu tekniğe sahip değildir; bu dillerde benzer tekniklerin isimleri var mı?

1
Derlemek istemediğiniz bazı kodları gösterebilir misiniz? Bunun soruyu daha açık hale getireceğini düşünüyorum.
Süpürücü

1
Ayrıca bir dil seçebilir (veya hangi dili kullandığınızı ve sözdiziminin ne anlama geldiğini söyleyebilirsiniz). Açıkçası, örneğin, C # değil. İnternette "tür çıkarımı" hakkında çok fazla bilgi var, ancak burada geçerli olduğundan emin değilim.

5
Ben derlemek için herhangi bir dilde kod almaya çalışmıyorum, bir dilde jenerik uygulamak. Aynı zamanda bir adlandırma ve tasarım sorusu. Bu yüzden biraz agnostik. Terimleri bilmeden mevcut dillerde örnek ve doküman bulmayı zorlaştırır. Elbette bu eşsiz bir sorun değil mi?
Levi Morrison

Yanıtlar:


2

Bu sorun için belirli bir terim olup olmadığını bilmiyorum, ancak üç genel çözüm sınıfı var:

  • dinamik sevkiyat lehine somut türlerden kaçının
  • tür kısıtlamalarında yer tutucu tür parametrelerine izin ver
  • İlişkili türler / tür aileleri kullanarak tür parametrelerinden kaçının

Ve elbette varsayılan çözüm: tüm bu parametreleri hecelemeye devam edin.

Beton türlerinden kaçının.

Bir Iterablearayüzü şu şekilde tanımladınız :

interface <Element> Iterable<T: Iterator<Element>> {
    getIterator(): T
}

Bu, kullanıcılara Tyineleyicinin kesin beton tipini aldıkları için arayüze maksimum güç verir . Bu aynı zamanda bir derleyicinin satır içi gibi daha fazla optimizasyon uygulamasını sağlar.

Ancak, Iterator<E>dinamik olarak gönderilen bir arayüz ise, beton tipini bilmek gerekli değildir. Bu, Java'nın kullandığı çözümdür. Arayüz daha sonra şu şekilde yazılacaktır:

interface Iterable<Element> {
    getIterator(): Iterator<Element>
}

Bunun ilginç bir varyasyonu, Rust'un impl Traitbir soyut dönüş tipi ile işlevi bildirmenize izin veren, ancak çağrı türünde beton türünün bilineceğini bilerek (böylece optimizasyonlara izin verir) sözdizimidir. Bu, örtük tip parametresine benzer şekilde davranır.

Yer tutucu türü parametrelerine izin ver.

IterableArayüz şekilde yazmak mümkün olabilir, böylece eleman türü hakkında bilmeniz gerekmez:

interface Iterable<T: Iterator<_>> {
    getIterator(): T
}

Nerede T: Iterator<_>kısıtlamayı ifade “T bakılmaksızın eleman tip herhangi yineleyici,” dedi. Daha titiz bir şekilde bunu şöyle ifade edebiliriz: “ somut bir tür bilmek zorunda kalmadan“ bir tür var Elementyani ” . Bu, type-ifadesinin gerçek bir türü tanımlamadığı ve yalnızca bir tip kısıtlaması olarak kullanılabileceği anlamına gelir .TIterator<Element>ElementIterator<_>

Tür ailelerini / ilişkili türleri kullanın.

Örneğin, C ++ 'da bir türün tip üyeleri olabilir. Bu standart kitaplık boyunca yaygın olarak kullanılır, ör std::vector::value_type. Bu, tüm senaryolarda type parametresi sorununu gerçekten çözmez, ancak bir tür diğer türlere atıfta bulunabileceğinden, tek bir tür parametresi, ilgili türlerin bir ailesini tanımlayabilir.

Tanımlayalım:

interface Iterator {
  type ElementType
  fn next(): ElementType
}

interface Iterable {
  type IteratorType: Iterator
  fn getIterator(): IteratorType
}

Sonra:

class Vec<Element> implement Iterable {
  type IteratorType = VecIterator<Element>
  fn getIterator(): IteratorType { ... }
}

class VecIterator<T> implements Iterator {
  type ElementType = T
  fn next(): ElementType { ... }
}

Bu çok esnek görünüyor, ancak bunun tür kısıtlamalarını ifade etmeyi zorlaştırabileceğini unutmayın. Yazıldığı gibi Iterableherhangi bir yineleyici eleman türünü zorunlu kılmaz ve interface Iterator<T>bunun yerine beyan etmek isteyebiliriz . Ve şimdi oldukça karmaşık tipte bir hesapla uğraşıyorsunuz. Yanlışlıkla böyle bir tür sistemi kararsız hale getirmek çok kolaydır (veya belki de zaten?).

İlişkili türlerin tür parametreleri için varsayılanlar olarak çok uygun olabileceğini unutmayın. Örneğin, Iterablearabirimin öğe türü için yineleyici öğe türüyle genellikle ancak her zaman aynı olmayan ayrı bir tür parametresine ihtiyaç duyduğu ve yer tutucu türü parametrelerimiz olduğu varsayılarak, şunları söylemek mümkün olabilir:

interface Iterable<T: Iterator<_>, Element = T::Element> {
  ...
}

Bununla birlikte, bu sadece bir dil ergonomisi özelliğidir ve dili daha güçlü yapmaz.


Tür sistemleri zordur, bu nedenle diğer dillerde neyin işe yarayıp neyin işe yaramadığına bir göz atmak iyidir.

Örneğin , Rust Book'ta ilişkili türleri tartışan Gelişmiş Özellikler bölümünü okumayı düşünün . Ancak, jenerikler yerine ilişkili türler lehine bazı noktaların orada geçerli olduğunu unutmayın, çünkü dil alt türlere sahip değildir ve her özellik her tür için en fazla bir kez uygulanabilir. Yani Rust özellikleri Java benzeri arayüzler değildir.

Diğer ilginç tip sistemleri arasında çeşitli dil uzantılarına sahip Haskell bulunmaktadır. OCaml modülleri / functorları , tip nesnelere veya parametreli tiplere doğrudan karıştırılmadan, tip ailelerin nispeten düz bir versiyonudur. Java, tür sistemindeki sınırlamalar açısından dikkat çekicidir, örneğin, tür silme özelliğine sahip jenerikler ve değer türleri üzerinde herhangi bir jenerik yoktur. C # çok Java'ya benzer ancak artan uygulama karmaşıklığı pahasına bu sınırlamaların çoğundan kaçınmayı başarır. Scala, C # tarzı jenerikleri, Java platformunun üstünde Haskell tarzı karakterlerle entegre etmeye çalışır. C ++ 'ın aldatıcı basit şablonları iyi çalışılmıştır, ancak çoğu jenerik uygulamadan farklıdır.

Hangi modellerin yaygın olarak kullanıldığını görmek için bu dillerin standart kitaplıklarına (özellikle listeler veya karma tablolar gibi standart kitaplık koleksiyonları) bakmaya değer. Örneğin, C ++, farklı yineleyici özelliklerinden oluşan karmaşık bir sisteme sahiptir ve Scala, ince taneli toplama yeteneklerini özellik olarak kodlar. Java standart kitaplık arabirimleri bazen sağlam değildir, Iterator#remove()ancak iç içe sınıfları bir tür ilişkili tür olarak kullanabilir (örn. Map.Entry).

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.