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 Iterable
arayüzü şu şekilde tanımladınız :
interface <Element> Iterable<T: Iterator<Element>> {
getIterator(): T
}
Bu, kullanıcılara T
yineleyicinin 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 Trait
bir 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.
Iterable
Arayü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 Element
yani ” . 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 .T
Iterator<Element>
Element
Iterator<_>
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 Iterable
herhangi 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, Iterable
arabirimin öğ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
).