Parametrik türler, bir parametre alabilen herhangi bir türü ifade eder. Verse’te parametrik türleri, veri yapılarını ve işlemlerini genelleştirmek için kullanabilirsin. Parametrik türleri bağımsız değişken olarak kullanmanın iki yolu vardır: fonksiyonların içinde açık veya örtük türde bağımsız değişken olarak veya sınıfların içinde açık türde bağımsız değişken olarak.
Olaylar, parametrik türlerin yaygın bir örneğidir ve UEFN’deki cihazlarda kapsamlı bir şekilde kullanılır. Örneğin, Buton cihazı, bir oyuncu butonla her etkileşime geçtiğinde oluşan InteractedWithEvent olayı bulunur. Bir parametrik türün işleyişini görmek için Özel Geri Sayım Süreölçeri eğitimindeki CountdownEndedEvent’e göz at.
Açık Tür Bağımsız Değişkenleri
İki bağımsız değişken alan bir kutu düşün. first_item bir ItemOne başlatır, second_item bir ItemTwo başlatır; bunların her ikisi de type türündedir. Hem first_item hem de second_item, bir sınıfın açık bağımsız değişkenleri olan parametrik tür örnekleridir.
box(first_item:type, second_item:type) := class:
ItemOne:first_item
ItemTwo:second_itemtype, first_item ve second_item için tür bağımsız değişkeni olduğundan, box sınıfı bu iki türden herhangi biriyle oluşturulabilir. İki dize değerinden oluşan bir kutun, iki tamsayı değerinden oluşan bir kutun, bir dize ve bir tamsayı değerinden oluşan bir kutun ve hatta iki kutudan oluşan bir kutun olabilir!
Bir diğer örnekte MakeOption() fonksiyonunu ele alalım. Bu fonksiyon herhangi bir türü alır ve o türden bir seçenek döndürür.
MakeOption(t:type):?t = false
IntOption := MakeOption(int)
FloatOption := MakeOption(float)
StringOption := MakeOption(string)MakeOption() fonksiyonunu, bunun yerine array veya map gibi başka bir kapsayıcı türü döndürecek şekilde değiştirebilirsin.
Örtük Tür Bağımsız Değişkenleri
Fonksiyonlar için örtük tür bağımsız değişkenleri where anahtar sözcüğü kullanılarak tanımlanır. Örneğin, bir parametreyi alan ve döndüren bir ReturnItem() fonksiyonu düşünelim:
ReturnItem(Item:t where t:type):t = ItemBurada t, ReturnItem() fonksiyonunun örtük tür parametresidir. type türünün bir bağımsız değişkenini alır ve onu hemen döndürür. t türü, bu fonksiyona iletebileceğimiz Item türünü kısıtlar. Bu durumda t değeri tür türünde olduğu için ReturnItem() fonksiyonunu herhangi bir türle çağırabiliriz. Fonksiyonlarla örtük parametrik türleri kullanmanın nedeni, kendisine geçirilen türden bağımsız olarak kod yazmana olanak tanımasıdır.
Örneğin, aşağıdaki kodu yazmak yerine:
ReturnInt(Item:int):int = Item
ReturnFloat(Item:float):float = ItemTek bir fonksiyon yazılabilir.
ReturnItem(Item:t where t:type):t = ItemBu sayede ReturnItem() fonksiyonunun t türünü bilmesi gerekmez. Hangi işlemi gerçekleştirirse gerçekleştirsin t türünden bağımsız olarak çalışır.
t için kullanılacak gerçek tür, ReturnItem() fonksiyonunun nasıl kullanıldığına bağlıdır. Örneğin, ReturnItem() fonksiyonu 0,0 bağımsız değişkeniyle çağrılırsa t, float olur.
ReturnItem("t") # t is a string
ReturnItem(0.0) # t is a floatBurada "hello" ve 0,0 değerleri, ReturnItem() fonksiyonuna iletilen açık bağımsız değişkenlerdir (Item). Bunların her ikisi de çalışır çünkü Eşya örtük türü t değeridir ve herhangi bir tür olabilir.
Bir fonksiyonun örtük bağımsız değişkeni olarak parametrik türün başka bir örneği için box sınıfı üzerinde çalışan aşağıdaki MakeBox() fonksiyonunu düşünelim.
box(first_item:type, second_item:type) := class:
ItemOne:first_item
ItemTwo:second_item
MakeBox(ItemOneVal:ValOne, SecondItemVal:ValTwo where ValOne:type, ValTwo:type):box(ValOne, ValTwo) =
box(ValOne, ValTwo){ItemOne := ItemOneVal, ItemTwo := SecondItemVal}
Main():void =
MakeBox("A", "B")
MakeBox(1, "B")
Burada MakeBox() fonksiyonu, her ikisi de tür türünde olan FirstItemVal ve SecondItemVal adlı iki bağımsız değişken alır ve (tür, tür) türünde bir kutu döndürür. Burada tür kullanılması, MakeBox fonksiyonuna, döndürülen kutunun herhangi iki objeden oluşabileceğini söylediğimiz anlamına gelir. Bu obje dizi, dize, fonksiyon vb. olabilir. MakeBox() fonksiyonu her iki bağımsız değişkeni de Box fonksiyonuna gönderir, bunları kullanarak bir kutu oluşturur ve döndürür. Hem box hem de MakeBox() için fonksiyon çağrısı olarak aynı sözdiziminin kullanıldığına dikkat et.
Bunun yerleşik bir örneği, aşağıda verilen Harita kapsayıcı türünün fonksiyonudur.
Map(F(:t) : u, X : []t) : []u =
for (Y : X):
F(Y)Tür Kısıtlamaları
Bir ifadenin türü için kısıtlama belirtebilirsin. Şu anda desteklenen tek kısıtlama alt türdür ve yalnızca örtük türde parametreler için desteklenir. Örneğin:
int_box := class:
Item:int
MakeSubclassOfIntBox(NewBox:subtype_box where subtype_box:(subtype(int_box))) : tuple(subtype_box, int) = (NewBox, NewBox.Item)Bu örnekte, MakeSubclassOfIntBox() yalnızca IntBox alt sınıfı oluşturan bir sınıf geçirdiğinde derlenir çünkü SubtypeBox, (subtype(IntBox)) türüne sahiptir. type türünün, subtype(any) türünün kısaltması olarak görülebileceğine dikkat et. Diğer bir deyişle bu fonksiyon, her türü ifade eden herhangi türünün herhangi bir alt türünü kabul eder.
Kovaryans ve Kontravaryans
Kovaryans ve Kontravaryans, türler kompozit türlerde veya fonksiyonlarda kullanıldığında iki türün ilişkisini ifade eder. Bir bakımdan ilişkili olan iki tür (örneğin biri diğerinin alt sınıfı olduğunda), belirli bir kod parçasında nasıl kullanıldıklarına bağlı olarak birbirinin kovaryantı ya da kontravaryantıdır.
Kovaryant: Kod daha genel bir şey beklendiğinde daha spesifik bir tür kullanmak.
Kontravaryant: Kod daha spesifik bir tür beklediğinde daha genel bir tür kullanmak.
Örneğin, herhangi bir karşılaştırılabilir değerin kabul edildiği bir durumda (kayan sayı gibi) bir tamsayı kullanabilseydik, daha genel bir tür beklenirken daha spesifik bir tür kullandığımız için tamsayı değerimiz kovaryant olarak işlev görürdü çünkü. Aksi durumda, normalde tamsayı kullanılacakken herhangi bir karşılaştırılabilir değer kullanabilseydik, daha özel bir tür beklenirken daha genel bir tür kullandığımız için karşılaştırılabilir değerimiz kontravaryant gibi davranırdı.
Parametrik bir türde kovaryans ve kontravaryans örneği aşağıdaki gibi görünebilir:
MyFunction(Input:t where t:type):logic = trueBurada t, kontravaryant şeklinde fonksiyonun girdisi olarak kullanılıyor, mantık ise kovaryant şeklinde fonksiyonun çıktısı olarak kullanılıyor.
İki türün özellik itibarıyla birbirinin kovaryantı veya kontravaryantı olmadığını, onun yerine kodda nasıl kullanıkdıklarına bağlı olarak kovaryant ya da kontravaryant olarak hareket ettikleri unutulmamalıdır.
Kovaryant
Kovaryans, daha genel bir şey beklenirken daha spesifik bir şey kullanmak anlamına gelir. Bu durum genellikle bir fonksiyonun çıktısı için geçerli olur. Fonksiyonların girdisi olmayan bütün tür kullanımları kovaryant kullanımlardır.
Aşağıdaki genel parametrik tür örneğinde yük, kovaryant olarak işlev görüyor.
DoSomething():int =
payload:int = 0Örneğin, bir animal sınıfımız ve animal sınıfının alt sınıfı olan bir cat sınıfımız olsun. Ayrıca AdoptPet() fonksiyonuyla evcil hayvan sahiplendiren bir pet_sanctuary sınıfımız da var. Hangi türde evcil hayvan alacağımızı bilmediğimiz için AdoptPet() genel bir hayvan döndürür.
animal := class:
cat := class(animal):
pet_sanctuary := class:
AdoptPet():animal = animal{}Yalnızca kedilerle ilgilenen başka bir hayvan barınağımız olsun. Bu cat_sanctuary sınıfı, pet_sanctuary’nin bir alt sınıfıdır. Burası bir kedi barınağı olduğundan AdoptPet() fonksiyonunu animal yerine yalnızca cat döndürmek üzere geçersiz kılarız.
cat_sanctuary := class(pet_sanctuary):
AdoptPet<override>():cat = cat{}Bu durumda AdoptPet() fonksiyonunun cat dönüş türü, animal türüyle kovaryant olarak hareket eder. Orijinal tür daha genel bir şey kullanırken biz daha spesifik bir tür kullanıyoruz.
Bu durum kompozit türler için de geçerli olabilir. Bir cat dizi belirtildiğinde, bu diziyle bir animal dizisi başlatabiliriz. animal, alt sınıfı olan cat sınıfına dönüştürülemeyeceği için tersi işe yaramaz. Kedi dizisi hayvan dizisinin kovaryantıdır çünkü daha dar bir türe daha genel bir tür gibi davranırız.
CatArray:[]cat = array{}
AnimalArray:[]animal = CatArrayFonksiyon girdileri kovaryant olarak kullanılamaz. Aşağıdaki kod, AnimalExample() fonksiyonunun cat türündeki CatExample() fonksiyonuna atanması nedeniyle başarısız olur çünkü cat türü, AnimalExample() dönüş türü olamayacak şekilde fazla spesifiktir. CatExample() fonksiyonunu AnimalExample fonksiyonuna atayarak bu sırayı tersine çevirmek işe yarar çünkü cat, animal türünün bir alt türüdür.
CatExample:type{CatFunction(MyCat:cat):void} = …
AnimalExample:type{AnimalFunction(MyAnimal:animal):void} = CatExampleAşağıda, t değişkeninin yalnızca kovaryant olarak kullanıldığı başka bir örnek yer alıyor.
# The line below will fail because t is used only covariantly.
MyFunction(:logic where t:type):?t = falseKontravaryant
Kontravaryans, kovaryantın zıttıdır ve spesifik bir şey beklerken daha genel bir şeyin kullanılması anlamına gelir. Bu genellikle bir fonksiyonun girdisidir. Aşağıdaki genel parametrik tür örneğinde yük, kontravaryant olarak işlev görür.
DoSomething(Payload:payload where payload:type):voidHayvan barınağımızın yeni kedileri kabul ederken spesifik bir prosedür uyguladığını varsayalım. pet_sanctuary sınıfına RegisterCat() adlı yeni bir metot ekleriz.
pet_sanctuary := class:
AdoptPet():animal = animal{}
RegisterCat(NewAnimal:cat):void = {}cat_sanctuary sınıfımız için bir animal sınıfını tür parametresi olarak kabul etmek amacıyla bu metodu geçersiz kılarız çünkü her cat değerinin bir animal olduğunu zaten biliyoruz.
cat_sanctuary := class(pet_sanctuary):
AdoptPet<override>():cat = cat{}
RegisterCat<override>(NewAnimal:animal):void = {}Burada animal, cat sınıfının kontravaryantıdır çünkü daha spesifik bir şey gerekirken daha genel bir şey kullanıyoruz.
where ifadesi tarafından tanımlanan örtük bir türü kovaryant olarak kullanmak hataya neden olur. Örneğin, buradaki payload, kontravaryant olarak kullanılır ancak bir bağımsız değişken olarak tanımlanmadığı için hata verir.
DoSomething(:logic where payload:type) : ?payload = falseBu hatayı düzeltmek için bu kod bir tür parametresini dışlayacak şekilde aşağıdaki gibi yeniden yazılabilir:
DoSomething(:logic) : ?false = falseYalnızca kontravaryant kullanmak hataya neden olmaz fakat false yerine herhangi kullanılarak yeniden yazılabilir. Örneğin:
ReturnFirst(First:first_item, :second_item where first_item:type, second_item:type) : first_item = Firstsecond_item, type türünde olduğundan ve döndürülmediğinden bunu ikinci örnekte any ile değiştirebilir ve üzerinde tür kontrolü yapmaktan kaçınabiliriz.
ReturnFirst(First:first_item, :any where first_item:type) : first_item = Firstfirst_item türünün any veya false ile değiştirilmesi kesinliğin kaybedilmesine yol açar. Örneğin, aşağıdaki kodun derlemesi başarısız olur:
ReturnFirst(First:any, :any) :any = First
Main() : void =
FirstInt:int = ReturnFirst(1, "ignored")Bilinen Kısıtlamalar
Veri türleri için açık tür parametreleri arayüzlerle veya yapılarla değil yalnızca sınıflarla birlikte kullanılabilir. Parametrik türlerle ilgili devralmaya da izin verilmez. | Verse |
Özyineleme doğrudan olduğu sürece parametrik türler kendilerine özyinelemeli olarak referans verebilir. Parametrik türler diğer parametrik türlere özyinelemeli olarak referans veremez. | Verse |
Şu anda sınıflar yalnızca değişmez parametrik tür verilerini desteklemektedir. Örneğin, | Verse |
Açık tür parametreler, tıpkı örtük tür parametrelerinin bir fonksiyonla birleştirilebilmesi gibi bir sınıfla serbestçe birleştirilebilir. | Verse |