Погружение в Core Solidity. Часть 3
Обобщения и классы типов
Core Solidity вводит два новых механизма для повторного использования кода и полиморфизма: обобщения (generics) и классы типов (иногда также называемые трейтами, traits).
Обобщения реализуют параметрический полиморфизм: они позволяют писать функции и структуры данных, работающие одинаково для всех типов. В качестве примера определим полиморфную функцию тождества:
Здесь forall вводит новую переменную-тип T, область видимости которой ограничена определением функции.
Можно также определять обобщённые типы. Например, следующий тип Result, параметризованный типом полезной нагрузки в случае ошибки:
Обобщения весьма мощны, но сами по себе довольно ограничены. Большинство интересных операций не определены для всех типов вообще. Классы типов решают эту проблему: они позволяют задавать перегруженные, специфичные для каждого типа реализации одной и той же сигнатуры функции. В сочетании с ограничениями классов типов они предоставляют возможность писать обобщённые функции, полиморфные лишь над ограниченным подмножеством типов.
Класс типов — это просто спецификация интерфейса. Рассмотрим, например, определение класса типов, которые поддерживают операцию умножения:
Вместо конкретной функции wmul, которую мы определили выше для нашего типа wad с фиксированной точкой, более идиоматично создать экземпляр (в терминологии Rust — impl) класса типов Mul для wad. Это даёт единообразный синтаксис умножения для всех типов и позволяет использовать wad в функциях, обобщённых над любыми типами, реализующими Mul:
Если мы хотим написать функцию, принимающую любой тип, для которого определён экземпляр Mul, необходимо добавить ограничение в сигнатуру:
Простые обёрточные типы вроде wad встречаются очень часто. Один из особенно полезных классов типов при работе с ними — Typedef:
Функции abs (абстрагирование) и rep (представление) позволяют единообразно преобразовывать обёрточные типы во внутренние и наоборот, избегая синтаксического шума, связанного с необходимостью использовать сопоставление с образцом каждый раз при распаковке значения. Экземпляр для wad выглядел бы так:
Обратите внимание: параметры, следующие после имени класса (например, U в определении Typedef выше), являются «слабыми» — их значение однозначно определяется значением параметра T. Если вы знакомы с Haskell или Rust, то это по сути ассоциированный тип (associated type) (хотя, для тех, кто разбирается в системах типов, реализовано это с помощью ограниченной формы функциональных зависимостей). Проще говоря, для wad можно определить только один экземпляр Typedef: компилятор не разрешит одновременно объявить и wad:Typedef(uint256), и wad:Typedef(uint128). Это ограничение делает вывод типов значительно более предсказуемым и надёжным, избегая многих неоднозначностей, присущих полноценным многопараметрическим классам типов.
Обобщения и классы типов
Core Solidity вводит два новых механизма для повторного использования кода и полиморфизма: обобщения (generics) и классы типов (иногда также называемые трейтами, traits).
Обобщения реализуют параметрический полиморфизм: они позволяют писать функции и структуры данных, работающие одинаково для всех типов. В качестве примера определим полиморфную функцию тождества:
forall T . function identity(x : T) -> T {
return x;
}Здесь forall вводит новую переменную-тип T, область видимости которой ограничена определением функции.
Можно также определять обобщённые типы. Например, следующий тип Result, параметризованный типом полезной нагрузки в случае ошибки:
data Result(T) = Ok | Err(T)
Обобщения весьма мощны, но сами по себе довольно ограничены. Большинство интересных операций не определены для всех типов вообще. Классы типов решают эту проблему: они позволяют задавать перегруженные, специфичные для каждого типа реализации одной и той же сигнатуры функции. В сочетании с ограничениями классов типов они предоставляют возможность писать обобщённые функции, полиморфные лишь над ограниченным подмножеством типов.
Класс типов — это просто спецификация интерфейса. Рассмотрим, например, определение класса типов, которые поддерживают операцию умножения:
forall T . class T:Mul {
function mul(lhs : T, rhs : T) -> T;
}Вместо конкретной функции wmul, которую мы определили выше для нашего типа wad с фиксированной точкой, более идиоматично создать экземпляр (в терминологии Rust — impl) класса типов Mul для wad. Это даёт единообразный синтаксис умножения для всех типов и позволяет использовать wad в функциях, обобщённых над любыми типами, реализующими Mul:
instance wad:Mul {
function mul(lhs : wad, rhs : wad) -> wad {
return wmul(lhs, rhs);
}
}Если мы хотим написать функцию, принимающую любой тип, для которого определён экземпляр Mul, необходимо добавить ограничение в сигнатуру:
forall T . T:Mul => function square(val : T) -> T {
return Mul.mul(val, val);
}Простые обёрточные типы вроде wad встречаются очень часто. Один из особенно полезных классов типов при работе с ними — Typedef:
forall T U . class T:Typedef(U) {
function abs(x : U) -> T;
function rep(x : T) -> U;
}Функции abs (абстрагирование) и rep (представление) позволяют единообразно преобразовывать обёрточные типы во внутренние и наоборот, избегая синтаксического шума, связанного с необходимостью использовать сопоставление с образцом каждый раз при распаковке значения. Экземпляр для wad выглядел бы так:
instance wad:Typedef(uint256) {
function abs(u : uint256) -> wad {
return wad(u);
}
function rep(x : wad) -> uint256 {
match x {
| wad(u) => return u;
}
}
}Обратите внимание: параметры, следующие после имени класса (например, U в определении Typedef выше), являются «слабыми» — их значение однозначно определяется значением параметра T. Если вы знакомы с Haskell или Rust, то это по сути ассоциированный тип (associated type) (хотя, для тех, кто разбирается в системах типов, реализовано это с помощью ограниченной формы функциональных зависимостей). Проще говоря, для wad можно определить только один экземпляр Typedef: компилятор не разрешит одновременно объявить и wad:Typedef(uint256), и wad:Typedef(uint128). Это ограничение делает вывод типов значительно более предсказуемым и надёжным, избегая многих неоднозначностей, присущих полноценным многопараметрическим классам типов.
❤2