Verse supporta i tipi parametrici (tipi, previsti come argomenti) in due contesti distinti: argomenti di tipo esplicito delle classi o funzioni e argomenti di tipo implicito delle funzioni. Per il primo tipo la sintassi è la seguente:
class1(t : type) := class:
Property : t
Per le funzioni, il parametro di tipo non può attualmente essere utilizzato come riferimento in nessun punto utile.
F1(t : type) : void = {}
Main() : void =
X := class1(int){Property := 0}
F1(int)
Tieni presente che entrambi utilizzano la stessa sintassi della chiamata di funzione. Le classi sono anche attualmente limitate a non supportare i dati modificabili di tipo parametrico. Ad esempio:
class2(t : type) := class:
var Property : t
Ciò non è attualmente consentito. I parametri di tipo esplicito dei tipi di dati si possono utilizzare solo con le classi e non con le interfacce o le strutture. Anche l'ereditarietà relativa ai tipi parametrici non è consentita: non possono essere ereditati, né possono ereditare da altri tipi.
Infine, non possono fare riferimento a se stessi in modo ricorsivo. Ad esempio, questo non è consentito:
cons(t : type) := class:
CAR : t
CDR : ?cons(t)
Gli argomenti di tipo implicito per le funzioni sono introdotti con la parola chiave where
. Ad esempio:
F2(X:t where t:type) : t = X
t
è un parametro implicito (tipo) di Identity
. Il motivo per utilizzare i tipi parametrici impliciti con le funzioni è che si tratta di un modo di scrivere una volta sola il codice invariante per un particolare tipo, anziché doverlo scrivere per ogni tipo con cui la funzione viene utilizzata.
Ad esempio, invece di scrivere
IntIdentity(X : int) : int = X
FloatIdentity(X : float) : float = X
si potrebbe scrivere invece la funzione singola
Identity(X:t where t:type):t = X
. Ciò viene fornito con la garanzia che Identity
non ha bisogno di sapere quale sia il tipo particolare t
: a prescindere dall'operazione che esegue, è invariante rispetto al tipo t
. Un altro esempio canonico è la funzione Map
, riportata di seguito.
Map(F(:t) : u, X : []t) : []u =
for (Y : X):
F(Y)
Il tipo effettivo da utilizzare per t
dipende dall'utilizzo di Identity. Ad esempio, se Identity viene chiamato con argomento 0, t
è int
. Seguono alcuni altri esempi.
Identity("hello") # t è una stringa
Identity(0.0) # t è float
A sinistra della t:
specifica un vincolo sul tipo t
, dove tipo significa che non è presente alcun vincolo. L'unico vincolo attualmente supportato è il sottotipo, anche se i vincoli di supertipo e di combinazione saranno aggiunti in una versione successiva. Ad esempio,
class3 := class:
Property:int
F2(X:t where t:subtype(class3)) : tuple(t, int) = (X, X.Property)
Tieni presente che il tipo può essere visto come una sintassi abbreviata di subtype(any)
, o supertype(false)
, dove supertype
è supportato. Tali vincoli non sono attualmente supportati per i parametri di tipo esplicito.
Gli utilizzi di un tipo si possono suddividere in posizioni positive e negative. Si tratta rispettivamente di posizioni simili a quelle covarianti e contravarianti. L'utilizzo di un argomento diverso da quello di una funzione è positivo o covariante. Tutti gli utilizzi di int
negli esempi seguenti sono positivi.
F():int
X : int = 0
Use(X)
L'utilizzo di tipo sull'argomento di una funzione è considerato negativo o contravariante. Questa situazione si capovolge (da negativo a positivo o da positivo a negativo) per ogni posizione altrimenti negativa. Ad esempio, il tipo t
qui è utilizzato in modo positivo:
F(G(:t):void where t:type) : void
Qui è utilizzato in modo negativo:
F(:t where t:type) : void
Se un tipo implicito introdotto da una clausola where
viene utilizzato in modo strettamente positivo (senza utilizzi negativi), viene restituito un errore. Ad esempio:
F(:logic where t:type) : ?t = false
Ciò produce un errore, ma potrebbe essere riscritto per escludere un parametro di tipo come segue:
F(:logic) : ?false = false
Gli utilizzi solo negativi non restituiscono un errore, ma si possono riscrivere utilizzando any
invece di false
. Ad esempio:
Const(X:t, :u where t:type, u:type) : t = X
Questo si potrebbe riscrivere in:
Const(X:t, :any where t:type) : t = X
Sostituendo il tipo t
con any
o false
si perde precisione. Ad esempio, la compilazione di quanto segue non riesce:
Const(X:any, :any) : any = X
Main() : void =
Y : int = Const(1, "ignorato")
A parte le limitazioni sopra elencate, i parametri di tipo esplicito si possono combinare liberamente con una classe come i parametri di tipo implicito si possono combinare con una funzione. Ad esempio:
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