Язык Verse поддерживает параметрические типы (типы, ожидаемые в качестве аргументов) в двух различных контекстах: аргументы явного типа для классов или функций и аргументы неявного типа для функций. Для первого случая синтаксис будет следующим:
class1(t : type) := class:
Property : t
Для функций параметр типа в настоящее время не может использоваться в каком-либо полезном контексте.
F1(t : type) : void = {}
Main() : void =
X := class1(int){Property := 0}
F1(int)
Обратите внимание, что в обоих случаях используется одинаковый синтаксис вызова функции. На данный момент классы не поддерживают изменяемые данные параметрического типа. Пример:
class2(t : type) := class:
var Property : t
На текущий момент это невозможно. Что касается типов данных, параметры явного типа могут использоваться только с классами, но не с интерфейсами или структурами. Наследование по отношению к параметрическим типам также невозможно: от них нельзя наследовать, и сами они не могут наследовать от других типов.
Наконец, они не могут ссылаться на самих себя рекурсивно. К примеру, следующий код недопустим:
cons(t : type) := class:
CAR : t
CDR : ?cons(t)
Аргументы неявного типа для функций вводятся с помощью ключевого слова where
. Пример:
F2(X:t where t:type) : t = X
t
является параметром неявного типа для Identity
. Преимущество использования неявных параметрических типов с функциями заключается в том, что это позволяет писать универсальный код для нескольких типов данных сразу вместо написания кода для каждого из типов данных по отдельности.
Например, вместо
IntIdentity(X : int) : int = X
FloatIdentity(X : float) : float = X
мы можем написать
Identity(X:t where t:type):t = X
что будет гораздо универсальней. Благодаря этому объекту Identity
нет необходимости знать, к какому типу относится t
; какая бы операция не выполнялась, она будет инвариантна к типу t
. Другим классическим примером является функция Map
, представленная ниже.
Map(F(:t) : u, X : []t) : []u =
for (Y : X):
F(Y)
Фактический тип, который будет использоваться для t
, зависит от того, как используется Identity. К примеру, если Identity будет вызываться с аргументом 0, то t
будет иметь тип int
. Вот ещё несколько примеров.
Identity("привет") # t будет строкой (string)
Identity(0.0) # t будет переменной с плавающей запятой (float)
Слева от t:
указывается ограничение на тип t
, где слово type означает отсутствие ограничений. В настоящее время единственным поддерживаемым ограничением является подтип, при этом супертип и комбинированные ограничения появятся в будущих обновлениях. Пример:
class3 := class:
Property:int
F2(X:t where t:subtype(class3)) : tuple(t, int) = (X, X.Property)
Обратите внимание, что type можно рассматривать в качестве сокращения для subtype(any)
, или supertype(false)
в случаях, когда поддерживается supertype
. В настоящее время данные ограничения не поддерживаются для параметров явного типа.
Использование того или иного типа можно рассматривать в контексте разделения на так называемые «положительные» и «отрицательные» позиции. Они аналогичны ковариантной и контравариантной позициям соответственно. Использование кроме как в качестве аргумента функции является положительным или ковариантным. Все случаи использования int
в следующих примерах относятся к категории положительных.
F():int
X : int = 0
Use(X)
Использование типа для аргумента функции считается отрицательным или контравариантным. Оно будет меняться (с отрицательного на положительное или с положительного на отрицательное) для каждой позиции, отрицательной в противном случае. К примеру, тип t
в данном примере используется положительно:
F(G(:t):void where t:type) : void
А в следующем — уже отрицательно:
F(:t where t:type) : void
Если неявный тип, введённый при помощи ключевого слова where
, используется строго положительно (без отрицательного использования), это приведёт к ошибке. Пример:
F(:logic where t:type) : ?t = false
Это приведёт к ошибке, но для исключения параметра типа можно изменить код так:
F(:logic) : ?false = false
Исключительно отрицательное использование не будет приводить к ошибке, однако соответствующую часть можно переписать с использованием any
вместо false
. Пример:
Const(X:t, :u where t:type, u:type) : t = X
Можно переписать следующим образом:
Const(X:t, :any where t:type) : t = X
Замена типа t
на any
или false
будет сопровождаться потерей точности. Например, следующий код не скомпилируется:
Const(X:any, :any) : any = X
Main() : void =
Y : int = Const(1, "ignored")
Помимо перечисленных выше ограничений, параметры явного типа можно свободно сочетать с классом, так же как параметры неявного типа можно сочетать с функцией. Пример:
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