11. Модули и общий код
Узнайте о получении общедоступных имен и явных аргументов типа.
Wave Foundation
Причины разделения файлов
По мере роста программы ее легче найти, сгруппировав связанные функции вместе, а не помещая все функции в main.wave. Границы модулей определяют, какие имена будут доступны другому коду. Дженерики — это инструмент, который повторно использует одну и ту же задачу с разными типами, независимо от разделения файлов.
В этой главе мы создаем двухфайловую программу и изучаем псевдонимы модулей, выбор import, а также общие функции и структуры.
двухфайловая программа
Создайте helpers.wave и main.wave в одном каталоге.
helpers.wave Все:
pub fun double(value: i32) -> i32 {
return value * 2;
}
main.wave Все:
import("./helpers")::{
double
};
fun main() {
var result: i32 = double(21);
println("{}", result);
}
Результат выполнения:
42
Запустите wavec run main.wave в терминале. helpers.wave также не запускается отдельно. Необходимый источник подключается через import.
pub перед функцией в helpers указывает, что она может быть импортирована другими модулями. Вспомогательные функции, которые не нужно раскрывать внешнему миру, не нужно публиковать. Даже если вы измените внутреннюю реализацию модуля, вы можете уменьшить количество изменений в используемом коде, сохранив контракт публичной функции.
Базовая линия для относительных путей
./helpers относится к каталогу исходного файла, в котором создано предложение import. При запуске программы отделите ее от рабочей директории, в которой записан файл I/O. Шаги по поиску файла import и шаги по поиску input.txt во время работы различаются.
Для локального import расширение .wave можно опустить. Если вы разделили каталог, пропишите путь ./module в соответствии с местоположением. Не путайте локальные относительные пути с путями, которые получают имена зависимостей пакетов.
Выберите import и псевдоним.
Выбор import приводит к использованию непосредственно в текущем файле только желаемых общедоступных имен. Если существует конфликт имен или вы хотите определить, какому модулю принадлежит функция, используйте псевдоним.
import("std::string::len" as strings);
fun main() {
var length: i32 = strings::len("Wave");
println("{}", length);
}
Результат выполнения:
4
strings — это псевдоним модуля, определенный в этом файле. strings::len использует имя модуля. Точка для доступа к полям и :: для разделения модулей — это разные обозначения.
Не используйте опцию import и псевдоним import вместе в одном предложении. Каким бы ни был ваш стиль, используйте его последовательно во всем файле, чтобы происхождение имени было легко читаемо.
Стандартные библиотеки и пакеты
Путь std:: указывает на стандартную библиотеку. Пользователь публикует API и import необходимые модули. Не все функции стандартной библиотеки автоматически помещаются в текущее пространство имен.
Пути к внешним пакетам начинаются с имени пакета. Местоположение пакета определяется параметрами компилятора или менеджером пакетов. Сначала изучите границы с помощью локальных модулей, а затем научитесь управлять зависимостями в Vex Как использовать.
Используйте одну и ту же функцию для каждого типа
Следующие функции возвращают свои входные данные дословно: Для i32 и str используйте параметр типа T, чтобы избежать написания одного и того же кода дважды.
fun identity<T>(value: T) -> T {
return value;
}
fun main() {
var number: i32 = identity<i32>(42);
var text: str = identity<str>("Wave");
println("{} {}", number, text);
}
Результат выполнения:
42 Wave
T — это место для ввода типа, а не целочисленного значения, передаваемого во время выполнения. Вызовите его, указав аргумент типа, например <i32>. Обычные пользовательские универсальные функции не пропускают аргументы типа.
identity не дублирует строковые байты, выделяя их заново. Возвращает значение как есть. Грамматика дженериков не меняет правила копирования и владения данными.
Операции, требуемые родовым телом
Наличие параметра типа не означает, что все операции можно использовать со всеми типами. minimum ниже следует использовать как фактический тип, который можно сравнивать.
fun minimum<T>(left: T, right: T) -> T {
if (left < right) {
return left;
}
return right;
}
fun main() {
var small: i32 = minimum<i32>(7, 4);
var wide: i64 = minimum<i64>(100, 20);
println("{} {}", small, wide);
}
Результат выполнения:
4 20
Если вы измените аргумент типа, < и возвращаемый результат, используемый в тексте, должен быть соответствующего типа. При чтении общих ошибок проверяйте как вызываемую комбинацию типов, так и операцию, требуемую телом функции.
универсальная структура
Давайте создадим Pair, который связывает два разных значения.
struct Pair<A, B> {
first: A;
second: B;
}
fun main() {
var item: Pair<i32, str> = Pair<i32, str> {
first: 7,
second: "seven"
};
println("{} {}", item.first, item.second);
}
Результат выполнения:
7 seven
Pair<i32, str> и Pair<i64, str> — это разные конкретные типы. Порядок аргументов типа также имеет значение. Прочтите декларацию и код генерации, чтобы узнать, где определяются типы first и second.
Имя и контракт API опубликованы.
Публикуя функцию, вы указываете не только имя, но и входную единицу, возвращаемое значение, ошибку и владельца. Например, цикл, который напишет вызывающая сторона, зависит от того, читает ли read максимальную длину или точную длину.
pub — это общедоступная область между модулями Wave. Это отличается от export (c), который экспортирует внешние символы для вызова других языков. Вы можете увидеть полный пример, связывающий два языка, по адресу См. FFI..
Упражнение и полное решение
Создайте общедоступную функцию square на math.wave и вызовите ее как псевдоним на main.wave, чтобы напечатать степень 3 и 5.
math.wave:
pub fun square(value: i32) -> i32 {
return value * value;
}
main.wave:
import("./math" as math);
fun main() {
println("{} {}", math::square(3), math::square(5));
}
Результат выполнения:
9 25
Если ошибка заключается в том, что функция исчезла, сначала проверьте путь import и pub. Если существует конфликт имен, проверьте, имеет ли вызов псевдоним. Если это ошибка типа, проверьте ввод функции и тип переданного аргумента. Не пытайтесь решить разные проблемы с помощью одной коррекции пути.
C Функция импорта
extern(c) fun puts(text: ptr<i8>) -> i32;
За именем ABI может следовать фактическое имя символа в виде строки.
extern(c, "native_symbol") fun local_name(value: i32) -> i32;
Wave Функция экспорта
export(c) fun wave_add(left: i32, right: i32) -> i32 {
return left + right;
}
extern и export можно использовать как отдельные функции и блоки. Экспортируемая функция должна иметь специальную сигнатуру ABI и, следовательно, не может быть универсальной.
Свойство целевого состояния
Свойства целевого условия можно прикрепить к элементам верхнего уровня.
#[target(os="linux", arch="x86_64")]
extern(c) fun platform_call(value: i32) -> i32;
Ключами условий являются arch, os, env, abi, а свойства применяются к следующему элементу верхнего уровня.
Подключитесь с помощью функции C, которую вы написали сами.
Эта лабораторная работа предназначена для собственной среды с компилятором C. Объединяет целочисленную функцию без выделения библиотеки или обработки строк.
native.c:
#include <stdint.h>
int32_t native_double(int32_t value) { return value * 2; }
main.wave:
extern(c) fun native_double(value: i32) -> i32;
fun main() {
println("{}", native_double(21));
}
Запустите Linux/macOS с терминала в том же рабочем каталоге.
cc -c native.c -o native.o
wavec build main.wave native.o -o ffi-example
./ffi-example
Ожидаемый результат: 42. В оболочке разработчика MSVC в Windows создайте object с cl /c native.c /Fonative.obj и подключитесь к wavec build main.wave native.obj -o ffi-example.exe. Исходная и целевая архитектуры object должны быть одинаковыми. В примерах используются только небольшие значения. Чтобы передать большое значение в функцию C, диапазон умножения страницы C также должен быть гарантирован отдельно.
Пути к локальным файлам начинаются с ./.