Данные и управление

Функции и обобщения

Перейдите от именованных функций и лямбд к обобщениям первого ранга и добавляйте ограничение лишь для нужного протокола.

Функция даёт повторно используемому преобразованию имя и объявляет типы на его границе. Начинайте с конкретной именованной функции. Для короткого поведения, передаваемого как значение, используйте лямбду. Параметр типа нужен только тогда, когда одну и ту же связь следует сохранить для нескольких типов.

Выполните всю последовательность в одной программе

Сохраните этот код в файле functions-generics.tpz:

TOPAZ
record Label derives Show {
    text: string,
}

function applyTwice(value: int, step: (int) -> int) -> int {
    step(step(value))
}

function firstOr<T>(values: Array<T>, fallback: T) -> T {
    match values {
        case [] => fallback
        case [first, ..] => first
    }
}

function showValue<T: Show>(value: T) -> string {
    Show.show(value)
}

let increment: (int) -> int = value => value + 1
let first: string = firstOr(["Topaz", "Classic"], "none")
let shown = showValue(Label { text: "docs" })

print("{applyTwice(3, increment)}")
print(first)
print(shown)

Сначала проверьте программу, затем запустите:

BASH
topaz check functions-generics.tpz
topaz run functions-generics.tpz

Результат:

Вывод
5
Topaz
Label { text: docs }

Начните с именованной функции

applyTwice объявляет два параметра и результат int. Последнее выражение тела становится результатом, поэтому отдельный return здесь не нужен. Используйте return, когда важно явно показать ранний выход.

Параметр step имеет тип (int) -> int: вызывающий код обязан передать функцию, которая принимает и возвращает int. increment — лямбда такой формы. Лямбда записывается через =>, хранится как обычное значение и захватывает видимые связывания по правилам замыканий.

Добавьте параметр типа, когда повторяется связь

Функции firstOr<T> неважно, лежат ли в массиве строки, целые числа или значения другого типа. Она обещает только, что элементы, запасное значение и результат имеют один тип T. В этом вызове переданы строки, поэтому вывод типов выбирает T = string; писать аргумент типа вручную не требуется.

Это полиморфизм первого ранга: именованная функция является обобщённой, а каждый вызов выбирает одну конкретную подстановку. Такой приём полезен, когда один алгоритм действительно сохраняет связь типов. Не стоит вводить обобщение лишь для того, чтобы отложить выбор типа.

Добавьте ограничение только для операции в теле

showValue<T: Show> вызывает Show.show, поэтому телу нужно подтверждение, что T соответствует статическому протоколу Show. Объявление Label derives Show предоставляет такое соответствие номинальному типу. Структурное значение или базовый тип не начинает соответствовать протоколу автоматически только потому, что кажется пригодным для вывода.

Ограничение — следующий шаг после обычного обобщения, а не исходный выбор. Именованная обобщённая функция может потребовать не зависящее от порядка сочетание вроде T: Show + JSON, если её тело действительно использует операции обоих протоколов.

Выберите наименьшую подходящую форму

ЗадачаЧто выбратьЧто это сообщает читателю
Повторно используемое поведение со стабильным именемКонкретную именованную функциюПараметры и результат образуют публичный контракт.
Короткое поведение, которое передают или хранят как значениеЛямбдуОкружающий контекст задаёт конкретные типы параметров.
Один алгоритм, сохраняющий связь для разных типовИменованную обобщённую функцию без ограниченийОдин и тот же T связывает входы и результат.
Обобщённое тело, вызывающее статическую операцию протоколаИменованную обобщённую функцию с ограничениемОграничение точно перечисляет доступные телу операции.

Вызовы, значения по умолчанию и переменное число аргументов

Параметры неизменяемы. Значение по умолчанию вычисляется, когда аргумент пропущен, и должно быть литералом или выражением const; оно не может ссылаться на другой параметр. Параметр переменной длины записывается ...args: T, обязан быть последним и внутри функции имеет тип Array<T>.

При вызове сначала один раз вычисляется сама вызываемая функция, затем слева направо — позиционные аргументы и аргументы с развёртыванием. Именованные аргументы следуют после них. Развёртывание массива может пополнять только хвост переменной длины, а не произвольный фиксированный параметр.

Обычный путь вызова обобщённой функции — вывод типов. Там, где разрешён точный список явных аргументов типов, он должен быть полным и согласовываться со значениями и ожидаемым результатом. Частичный список является статической ошибкой.

Частая ошибка: искать обобщённую лямбду

Короткое безымянное поведение записывается лямбдой, чьи конкретные типы параметров задаёт контекст. Параметры типов и ограничения протоколов объявляются только у именованных функций. Если поведению нужен самостоятельный повторно используемый обобщённый контракт, дайте ему имя и объявите этот контракт один раз.

Точные ограничения

  • Обобщённые функции имеют первый ранг; функции высших рангов пока отложены.
  • Лямбды, типы функций, псевдонимы, номинальные объявления, методы получателя и методы протокола не объявляют ограничений.
  • Обобщённые лямбды, значения параметров типов по умолчанию, аннотации вариантности, динамическая диспетчеризация протоколов и произвольные ограничения черт пока отложены.
  • Тело обобщённой функции имеет ровно объявленные ограничения и не может молча потребовать более сильную операцию.

Первый урок о функциях и лямбдах находится на странице Значения и функции. Формы типов функций и обобщённых типов описаны в разделе Типы, а захват и время жизни — в разделе Связывания, области и замыкания.