Универсальные шаблоны
Значения функции, методы, свойства и агрегатные типы, например классы, записи и размеченные объединения, в F# могут быть универсальными. Универсальные конструкции содержат по меньшей мере один параметр типа, который обычно задается пользователем такой конструкции. Универсальные функции и типы позволяют писать код, который работает с множеством типов без повторения кода для каждого из них. В F# можно легко сделать код универсальным, так как зачастую код неявно определяется как универсальный механизмами определения типов и автоматического обобщения в компиляторе.
Синтаксис
// Explicitly generic function.
let function-name<type-parameters> parameter-list =
function-body
// Explicitly generic method.
[ static ] member object-identifier.method-name<type-parameters> parameter-list [ return-type ] =
method-body
// Explicitly generic class, record, interface, structure,
// or discriminated union.
type type-name<type-parameters> type-definition
Remarks
Объявления явно универсальной функции или явно универсального типа похоже на объявление неуниверсальных функций или типов, за исключением задания (и использования) параметров типа в угловых скобках после имени функции или типа.
Объявления часто являются неявно универсальными. Если вы не указываете полностью тип каждого параметра, составляющего функцию или тип, компилятор пытается определить тип каждого параметра, значения и переменной из написанного кода. Дополнительные сведения см. в статье Определение типа. Если код для типа или функции не ограничивают типы параметров иным образом, то функция или тип являются неявно универсальными. Этот процесс называется автоматическим обобщением. Автоматическое обобщение имеет некоторые ограничения. Например, если компилятору F# не удается определить типы для универсальной конструкции, он выдает ошибку, ссылающуюся на ограничение под названием ограничение значения. В этом случае может потребоваться добавить некоторые заметки с типом. Дополнительные сведения об автоматическом обобщении и ограничении значения, а также об изменении кода для решения этой проблемы см. в статье Автоматическое обобщение.
В приведенном выше синтаксисе type-parameters — это разделенный запятыми список параметров, представляющих неизвестные типы, каждый из которых начинается с одинарной кавычки. Может также присутствовать предложение ограничения, которое дополнительно ограничивает перечень допустимых типов для этого параметра типа. Синтаксис для различных предложений ограничения и прочие сведения об ограничениях см. в статье Ограничения.
Компонент type-definition в синтаксисе совпадает с определением типа для неуниверсального типа. Он содержит параметры конструктора для типа класса, необязательное предложение as
, символ равенства, поля записей, предложение inherit
, варианты для размеченного объединения, привязки let
и do
, определения элементов и все остальное, что разрешено в определении неуниверсального типа.
Остальные компоненты синтаксиса являются такими же, что и в неуниверсальных функциях и типах. Например, object-identifier — это идентификатор, представляющий сам содержащий объект.
Свойства, поля и конструкторы не могут быть более универсальными, чем включающий тип. Кроме того, значения в модуле не могут быть универсальными.
Неявно универсальные конструкции
Когда компилятор F# определяет типы в коде, он автоматически рассматривает как универсальную любую функцию, которая может быть таковой. Если указать тип явно, например, тип параметра, автоматическое обобщение не выполняется.
В следующем примере кода makeList
является универсальной, хотя ни она, ни ее параметры не объявляются явно как универсальные.
let makeList a b =
[a; b]
Подпись функции определяется как 'a -> 'a -> 'a list
. Обратите внимание, что a
и b
в этом примере определяются, как имеющие одинаковый тип. Это вызвано тем, что они включаются в список вместе, а все элементы списка должны иметь один тип.
Кроме того, функцию можно сделать универсальной, применив синтаксис с одинарной кавычкой в заметке типа, чтобы показать, что тип параметра является параметром универсального типа. В следующем коде function1
является универсальной, так как ее параметры объявляются указанным образом — как параметры типа.
let function1 (x: 'a) (y: 'a) =
printfn "%A %A" x y
Явно универсальные конструкции
Вы также можете сделать функцию универсальной, явно объявив ее параметры типа в угловых скобках (<type-parameter>
). Это проиллюстрировано в следующем коде.
let function2<'T> (x: 'T) (y: 'T) =
printfn "%A, %A" x y
Использование универсальных конструкций
При использовании универсальных функций или методов вам может быть не нужно указывать аргументы типа. Компилятор использует определение типа для выведения подходящих аргументов типа. Если по-прежнему присутствует неоднозначность, можно указать аргументы типа в угловых скобках, разделяя их запятыми.
Следующий код показывает использование функций, определенных в предыдущих разделах.
// In this case, the type argument is inferred to be int.
function1 10 20
// In this case, the type argument is float.
function1 10.0 20.0
// Type arguments can be specified, but should only be specified
// if the type parameters are declared explicitly. If specified,
// they have an effect on type inference, so in this example,
// a and b are inferred to have type int.
let function3 a b =
// The compiler reports a warning:
function1<int> a b
// No warning.
function2<int> a b
Примечание
Сослаться на универсальный тип по имени можно двумя способами. Например, list<int>
и int list
представляют два способа сослаться на универсальный тип list
с одним аргументом типа int
. Последняя форма обычно используется только со встроенными типами F#, такими как list
и option
. При наличии нескольких аргументов типа обычно используется синтаксис Dictionary<int, string>
, но можно также использовать синтаксис (int, string) Dictionary
.
Подстановочные знаки как аргументы типа
Чтобы указать, что аргумент типа должен определяться компилятором, можно использовать символ подчеркивания или подстановочный знак (_
) вместо именованного аргумента типа. Это показано в приведенном ниже коде.
let printSequence (sequence1: Collections.seq<_>) =
Seq.iter (fun elem -> printf "%s " (elem.ToString())) sequence1
Ограничения в универсальных типах и функциях
В определении универсального типа или универсальной функции можно использовать только те конструкции, которые доступны для параметра универсального типа. Это необходимо для проверки вызовов функций и методов во время компиляции. Если вы объявляете параметры типа явно, можно применить к параметру универсального типа явное ограничение, чтобы уведомить компилятор о доступности некоторых методов и функций. Тем не менее, если разрешить компилятору F# выводить универсальные типы параметров, он самостоятельно определит подходящие ограничения. Дополнительные сведения см. в статье Ограничения.
Статически разрешаемые параметры типов
Существует два вида параметров типа, которые можно использовать в программах на языке F#. Первый — это параметры универсального типа, описанные в предыдущих разделах. Первый вид параметров типа эквивалентен параметрам универсального типа, которые используются в таких языках, как Visual Basic и C#. Другой вид параметров типа имеется только в F# и называется статически разрешаемым параметром типа. Сведения об этих конструкциях см. в статье Статически разрешаемые параметры типов.
Примеры
// A generic function.
// In this example, the generic type parameter 'a makes function3 generic.
let function3 (x : 'a) (y : 'a) =
printf "%A %A" x y
// A generic record, with the type parameter in angle brackets.
type GR<'a> =
{
Field1: 'a;
Field2: 'a;
}
// A generic class.
type C<'a>(a : 'a, b : 'a) =
let z = a
let y = b
member this.GenericMethod(x : 'a) =
printfn "%A %A %A" x y z
// A generic discriminated union.
type U<'a> =
| Choice1 of 'a
| Choice2 of 'a * 'a
type Test() =
// A generic member
member this.Function1<'a>(x, y) =
printfn "%A, %A" x y
// A generic abstract method.
abstract abstractMethod<'a, 'b> : 'a * 'b -> unit
override this.abstractMethod<'a, 'b>(x:'a, y:'b) =
printfn "%A, %A" x y