Język Verse obsługuje typy parametryczne (typy oczekiwane jako argumenty) w dwóch odrębnych kontekstach: jako argumenty typu jawnego w odwołaniu do klas lub funkcji oraz jako argumenty typu niejawnego w odwołaniu do funkcji. W pierwszym przypadku składnia jest następująca:
class1(t : type) := class:
Property : t
W przypadku funkcji w żadnym użytecznym miejscu nie może w danej chwili występować odwołanie do parametru typu.
F1(t : type) : void = {}
Main() : void =
X := class1(int){Property := 0}
F1(int)
Zauważ, że w obydwu przykładach zastosowano taką samą składnię, jak przy wywoływaniu funkcji. Obecnie klasy również podlegają ograniczeniu i nie obsługują modyfikowalnych danych typu parametrycznego. Na przykład:
class2(t : type) := class:
var Property : t
Obecnie jest to niedozwolone. Parametrów typu jawnego pod względem typów danych można używać wyłącznie z klasami i nie można ich stosować do interfejsów ani struktur. Dziedziczenie powiązane z typami parametrycznymi również jest niedozwolone – nie mogą być one źródłem dziedziczenia ani same nie mogą dziedziczyć z innych typów.
Nie mogą one również odwoływać się rekursywnie do samych siebie. Niedozwolony jest na przykład następujący zapis:
cons(t : type) := class:
CAR : t
CDR : ?cons(t)
Argumenty typu niejawnego do funkcji wprowadza się za pomocą słowa kluczowego where
. Na przykład:
F2(X:t where t:type) : t = X
t
jest parametrem (typem) niejawnym Identity
. Niejawnych typów parametrycznych używa się wraz z funkcjami, aby móc jeden raz napisać niezmienny kod niezależny od konkretnego typu, zamiast pisać go dla każdego typu, z którym stosuje się funkcję.
Na przykład zamiast zapisu
IntIdentity(X : int) : int = X
FloatIdentity(X : float) : float = X
można zastosować pojedynczy zapis
Identity(X:t where t:type):t = X
Zyskuje się w ten sposób gwarancję, że Identity
nie musi znać konkretnego typu t
– że bez względu na rodzaj wykonywanej operacji nie zmienia się w zależności od typu t
. Innym sztandarowym przykładem jest podana poniżej funkcja Map
.
Map(F(:t) : u, X : []t) : []u =
for (Y : X):
F(Y)
Faktyczny typ zastosowany w odwołaniu do t
będzie zależał od sposobu wykorzystania funkcji Identity. Jeśli na przykład funkcja Identity zostanie wywołana za pomocą argumentu 0, typem t
będzie int
. Poniżej przedstawiamy kilka dodatkowych przykładów.
Identity("hello") # t jest ciągiem tekstowym
Identity(0.0) # t jest liczbą zmiennoprzecinkową
Z lewej strony członu t:
określa się ograniczenie typu t
, przy czym type oznacza brak ograniczenia. Obecnie jedynym obsługiwanym ograniczeniem jest podtyp (subtype), choć w przyszłych wydaniach dodane zostaną ograniczenie nadtypu (supertype) oraz ograniczenia łączone. Na przykład:
class3 := class:
Property:int
F2(X:t where t:subtype(class3)) : tuple(t, int) = (X, X.Property)
Zwróć uwagę, że w sytuacjach, w których obsługiwane jest ograniczenie subtype
, ograniczenie type można potraktować jako skrót ograniczenia subtype(any)
lub supertype(false)
. Takie ograniczenia nie są obecnie obsługiwane w parametrach typu jawnego.
Zastosowania ograniczenia type można podzielić na tak zwane pozycje pozytywne i negatywne. Odpowiadają one kolejno pozycjom kowariantnym i kontrawariantnym. Zastosowanie "inne niż" w charakterze argumentu funkcji ma charakter pozytywny lub kowariantny. Wszystkie zastosowania int
w poniższych przypadkach są przykładami użycia pozytywnego.
F():int
X : int = 0
Use(X)
Użycie ograniczenia type w argumencie funkcji traktowane jest jako użycie negatywne lub kontrawariantne. Sytuacja odwraca się (z negatywnej na pozytywną lub z pozytywnej na negatywną) dla każdej pozycji w innym przypadku negatywnej. W tym przykładzie typ t
został zastosowany w sposób pozytywny:
F(G(:t):void where t:type) : void
Tutaj zastosowano go w sposób negatywny:
F(:t where t:type) : void
Jeśli typ niejawny wprowadzony za pomocą klauzuli where
zostanie zastosowany w sposób ściśle pozytywny (bez użyć negatywnych), spowoduje to błąd. Na przykład:
F(:logic where t:type) : ?t = false
Taki zapis wygeneruje błąd, jednak można go zmienić w celu wykluczenia parametru typu w następujący sposób:
F(:logic) : ?false = false
Użycia wyłącznie negatywne nie powodują błędu, jednak można je zmienić, wykorzystując any
zamiast false
. Na przykład:
Const(X:t, :u where t:type, u:type) : t = X
Zapis ten można zmodyfikować następująco:
Const(X:t, :any where t:type) : t = X
Zastąpienie typu t
członem any
lub false
powoduje utratę dokładności. Na przykład poniższego kodu nie da się skompilować:
Const(X:any, :any) : any = X
Main() : void =
Y : int = Const(1, "ignored")
Oprócz wymienionych powyżej ograniczeń, parametry typu jawnego można swobodnie łączyć z klasą, podobnie jak parametry typu niejawnego można łączyć z funkcją. Na przykład:
class4(t : type) := class:
Property : ?t
Flatten(X:?class4(t) where t:type):?t =
if (Y := X):
Y.Property
else:
false
Main() : void =
X := class4(int){Property := option{1}}
Flatten(option{X}) = X.Property