Поделиться через


Универсальные шаблоны (F#)

Функциональные значения, методы, свойства и типы-агрегаты, например классы, записи и размеченные объединения, в F# могут быть универсальными. Универсальные конструкции содержат по меньшей мере один параметр типа, который обычно задается пользователем такой конструкции. Универсальные функции и типы позволяют создавать код, способный работать с различными типами, не повторяя при этом код для каждого отдельного типа. Сделать код универсальным в F# может быть довольно просто, так как зачастую код неявно выводится в универсальной форме благодаря определению типов в компиляторе и механизмам автоматической универсализации.

// Explicitly generic function.
let function-name<type-parameters> parameter-list =
   function-body

// Explicitly generic method.
[ static ] member object-identifer.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

Заметки

Явное объявление универсальной функции или типа похоже на неуниверсальный аналог; единственное различие состоит в указании (и использовании) параметров типов в угловых скобках после имени функции или типа.

Объявления зачастую неявно становятся универсальными. При неполном указании типов некоторых параметров, составляющих функцию или тип, компилятор пытается определить тип всех параметров, значений и переменных по создаваемому пользователем коду. Дополнительные сведения см. в разделе Вывод типа (F#). Если код типа или функции не ограничивает типы параметров каким-либо особым образом, функция или тип неявно универсализируется. Данный процесс называется автоматической универсализацией. Существуют некоторые ограничения на автоматическую универсализацию. Например, если компилятору F# не удается определить типы для универсальной конструкции, то он выдает ошибку, относящуюся к ограничению, называемому ограничением значений. В этом случае к типу следует добавить определенные пояснения. Дополнительные сведения об автоматической универсализации и ограничении значений, а также об изменении кода для решения возникающих проблем см. в разделе Автоматическое обобщение (F#).

В предшествующей синтаксической конструкции type-parameters — это список параметров с разделителями-запятыми, представляющих неизвестные типы. Каждый параметр начинается с одиночной кавычки, за которой может следовать предложение ограничения, дополнительно ограничивающее набор типов, которые могут использоваться в качестве данного параметра. Синтаксис предложений ограничения различных типов и прочие сведения об ограничениях см. в разделе Ограничения (F#).

Синтаксическое обозначение 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

Явная универсализация конструкций языка

Можно также сделать функцию универсальной, явно объявив ее параметры типа в угловых скобках (< >). Это проиллюстрировано в следующем коде.

let function2<'T> x y =
    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#).

Статически разрешаемые параметры типов

Существует два вида параметров типов, которые можно использовать в программах на F#. Первый вид — это параметры универсальных типов, описанные в предыдущих разделах. Первый вид параметров типов эквивалентен параметрам универсальных типов, используемых в таких языках, как Visual Basic и C#. Другой вид параметров типов имеется только в F#. Это статически разрешаемые параметры типов. Дополнительные сведения об этих конструкциях см. в разделе Статически разрешаемые параметры типа (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

См. также

Ссылки

Статически разрешаемые параметры типа (F#)

Ограничения (F#)

Основные понятия

Универсальные шаблоны в платформе .NET Framework

Другие ресурсы

Справочник по языку F#

Типы языка F#

Автоматическое обобщение (F#)