Примечание о библиотечных именах: вспомогательные функции и вызовы методов вне минимума стандартной библиотеки (
ПогодаAPI.текущая,split,заглавная,join,добавить,format,Date.parse,CSV.прочитатьи т.д.) являются иллюстративными заполнителями, а не каноническими API.
«Один способ сказать» — Топаз держит поверхность языка малой и замкнутой: у каждого замысла одна каноническая форма, зафиксированная в спецификации v5.2. Это руководство охватывает всю поверхность.
Основная философия
Одна форма для каждого замысла
Даже сложную логику можно выразить кратко и читаемо.
Глобальный синтаксис, локальное выражение
- Ключевые слова: Унифицированы на английском (
function,let,match,case) - Идентификаторы: Свободное использование любого языка, русского, Unicode и даже эмодзи
Всё является выражением
Конструкции вроде if, match, for и блоков возвращают значения.
Базовый синтаксис
Объявление переменных
// Неизменяемые переменные (по умолчанию)
let имя = "Алексей"
let возраст = 25
let активен = true
// Изменяемые переменные
let mut очки = 85
очки = 90 // Можно изменить
// Указание типа (опционально)
let расстояние: float = 3.14
let пользователи: Array<string> = ["Иван", "Мария"]Присваивание объединения с null (??=)
Оператор‑заявление для инициализации, когда цель — None/null.
let mut name: Option<string> = None
name ??= Some("guest")Определение функций
// Базовая функция
function приветствие(имя: string) -> string {
"Привет, {имя}!"
}
// Параметры по умолчанию
function вычислить(x: int, y: int = 10) -> int {
x + y
}
// Возврат записи
function получитьКоординаты() -> { x: int, y: int } {
{ x: 100, y: 200 }
}
// Функции высшего порядка
function преобразовать(данные: Array<int>, функция: (int) -> int) -> Array<int> {
map(данные, функция)
}В позициях типов публичная документация Topaz использует стрелочную форму (T) -> U для типов функций.
Условные выражения
// if выражение (возвращает значение)
let сообщение = if возраст >= 20 {
"Вы взрослый"
} else if возраст >= 13 {
"Вы подросток"
} else {
"Вы ребёнок"
}
// В стиле тернарного оператора
let статус = if онлайн { "Подключён" } else { "Отключён" }Сопоставление с образцом
// Базовое сопоставление
let результат = match значение {
case 1 => "Один"
case 2 => "Два"
case 3 => "Три"
case _ => "Другое"
}
// Сопоставление диапазонов
let оценка = match балл {
case 90..100 => "Отлично"
case 80..<90 => "Хорошо"
case 70..<80 => "Удовлетворительно"
case _ => "Неудовлетворительно"
}
// Структурное сопоставление
let скидка = match клиент {
case { уровень: "VIP", сумма } if сумма > 1000000 => 0.3
case { уровень: "VIP" } => 0.2
case { лояльностьЛет } if лояльностьЛет > 1 => 0.1
case _ => 0.0
}Списковые паттерны
Маркер остатка .. может появиться один раз: либо как весь паттерн
([..rest]), либо как последний элемент после фиксированных начальных
элементов.
match xs {
case [] => useEmpty()
case [head, ..tail] => useHead(head, tail)
case [x, y, ..rest] if rest.length > 0 => handle(x, y, rest)
}Сквозной пример (guard + rest-паттерн):
let описание = match числа {
case [] => "пусто"
case [один] => "один: {один}"
case [первый, ..остальные] if остальные.length > 3 => "длинная серия от {первый}"
case [первый, ..остальные] => "первый {первый}, остальных={остальные.length}"
}Конвейерные операции
Базовый конвейер
// Один шаг на строку, сверху вниз
let результат = исходныеДанные
|> нормализовать()
|> фильтровать(x => x > 0)
|> преобразовать(x => x * 2)
|> сортировать()Строка, начинающаяся с |> (или . / ?.), продолжает предыдущее
выражение, поэтому многострочный конвейер выше — одна инструкция.
Поэтапный текстовый конвейер
let обработаннаяСтрока = "привет мир"
|> split(_, " ")
|> map(_, слово => заглавная(слово))
|> join(_, " ")
|> добавить("!", _)
// Результат: "Привет Мир!"Сахар для конвейера
Короткие формы справа от |>:
let имена = ["Аня", "Борис", "Вера"]
let сводка = имена
|> .length // сахар свойства: (xs => xs.length)
|> format("количество=", _) // плейсхолдер: (n => format("количество=", n))
// Каждый `_` в ближайшем вызове получает одно и то же значение конвейера
let строка = имена |> format("первый из ", _, " это ", _)Система типов
Вывод типов
// Типы автоматически выводятся
let число = 42 // int
let текст = "привет" // string
let массив = [1, 2, 3, 4] // Array<int>
let объект = { // { имя: string, возраст: int }
имя: "Алексей",
возраст: 25
}Литеральные типы
// Ограничение типов точными значениями
type СветофорЦвет = "красный" | "жёлтый" | "зелёный"
type БросокКубика = 1 | 2 | 3 | 4 | 5 | 6
type КодСостояния = 200 | 404 | 500
function обработатьСигнал(цвет: СветофорЦвет) {
match цвет {
case "красный" => стоп()
case "жёлтый" => осторожно()
case "зелёный" => ехать()
// Компилятор проверяет, что все случаи обработаны!
}
}Объединённые типы
type ПользовательскийВвод = string | int | null
function обработать(ввод: ПользовательскийВвод) -> string {
match ввод {
case текст: string => "Строка: {текст}"
case число: int => "Число: {число}"
case null => "Ввод отсутствует"
}
}null и Option<T> связаны, но не тождественны. Используйте null в nullable-union типах вроде T | null, а Some(...) / None — для Option<T>. Операторы ?? и ?. работают с обеими моделями.
Циклы
Циклы for (выражения)
// Итерация по диапазону
for i in 1..10 {
print("Число: {i}")
}
// Итерация по массиву
for элемент in ["яблоко", "банан", "вишня"] {
print("Фрукт: {элемент}")
}
// for тоже возвращает значения!
let квадраты = for x in 1..5 { x * x } // [1, 4, 9, 16, 25]Шаг диапазона с by
// Шаг вперёд
for i in 0..10 by 2 { print("{i}") } // 0,2,4,6,8,10
// Шаг назад (отрицательный шаг)
for i in 10..0 by -3 { print("{i}") } // 10,7,4,1while, break и continue
Topaz предоставляет общие циклы как инструкции. while выполняет тело,
пока условие типа bool истинно; break выходит из самого
внутреннего цикла, а continue переходит к его следующей итерации.
let mut сумма = 0
let mut n = 1
while сумма + n <= 100 {
сумма = сумма + n
n = n + 1
}
print("сумма: {сумма}")
// break / continue нацелены на самый внутренний цикл
for элемент in [3, -1, 7, 120, 5] {
if элемент < 0 { continue }
if элемент > 99 { break }
print("{элемент}")
}while — инструкция и не порождает значения. for, используемый как
выражение, собирающее значения, не может содержать break/continue,
нацеленные на этот for, — используйте в таком случае for в роли
инструкции.
Коллекции
Массивы
// Базовые массивы
let числа = [1, 2, 3, 4, 5]
let фрукты = ["яблоко", "банан", "вишня"]
// Функциональные helper-имена
let большие = filter(числа, x => x > 2) // [3, 4, 5]
let квадраты = map(числа, x => x * x) // [1, 4, 9, 16, 25]
let сумма = reduce(числа, (acc, x) => acc + x, 0) // 15Принадлежность (in)
// Массивы/Списки/Множества
let ok1 = 3 in [1, 2, 3]
let ok2 = "a" in Set.of("a", "b")
// Ключи map
let hasId = "id" in userMap.keys
// Диапазоны
let inside = 5 in 1..10
let outside = 10 in 1..<10Записи и псевдонимы типов
// Литерал записи
let пользователь = {
имя: "Алексей",
возраст: 25,
email: "aleksey@example.com"
}
// Псевдоним типа записи
type Пользователь = {
имя: string,
возраст: int,
email: string,
}
// Значение записи с аннотацией псевдонима
let новыйПользователь: Пользователь = {
имя: "Мария",
возраст: 30,
email: "maria@example.com",
}Литерал обновления записи
Немутируемое обновление: мелкое копирование записи с изменением выбранных полей.
let user = { name: "Alice", age: 20, city: "Seoul" }
let updated = user{ age: user.age + 1, city: "Busan" }
// { name: "Alice", age: 21, city: "Busan" }Строковые шаблоны
Базовая интерполяция
let имя = "Алексей"
let возраст = 25
let приветствие = "Привет, {имя}! Вам {возраст} лет."Многострочные строки
Строки в тройных кавычках содержат несколько строк; пробельный префикс
закрывающего разделителя удаляется из каждой строки, а интерполяция
{expr} работает как обычно.
let баннер = """
Topaz v5.2
один способ сказать
"""Тегированные шаблоны
html"...", тегированные шаблоны с обратными кавычками и пользовательские теги остаются вне канонического Topaz. Канонические tagged templates — этоp"...",r"...",sh"..."иsql"..."со строками в двойных кавычках, однострочными или тройными.
Стандартизированные теги с сохранением метаданных и безопасностью:
let путь = p"/home/{user}/docs/{fileName}" // нормализация пути
let шаблон = r"^[a-z0-9_]+$" // регулярные выражения с лёгкими экранированиями
let команда = sh"grep {шаблон} {файл}" // безопасный shell‑шаблон (политики выполнения)
// SQL: параметры всегда привязываются (никакой прямой вставки строк)
let запрос = sql"SELECT * FROM users WHERE age > {возраст} AND city = {город}"
// Многострочный SQL сочетается с формой в тройных кавычках
let отчёт = sql"""
SELECT name, total
FROM orders
WHERE region = {регион}
ORDER BY total DESC
"""Асинхронная обработка
Асинхронная модель
Topaz определяет параллельную работу только через
concurrent: форма простого соединения и форма с тайм-аутом, чейelseвыполняется только по тайм-ауту. Нативныеasync/awaitи семантика automatic async остаются отложенными.
Параллельное выполнение
// Запуск нескольких задач параллельно
let панель = concurrent(timeout: 3s) {
погода: ПогодаAPI.текущая("Москва")
курс: ВалютаAPI.курсДоллара()
новости: НовостиAPI.заголовки(5)
} else {
{
погода: None,
курс: None,
новости: []
}
}Обработка ошибок
Тип Result
function безопасноеДеление(a: int, b: int) -> Result<int, string> {
if b == 0 {
Err("Нельзя делить на ноль")
} else {
Ok(a / b)
}
}
// Просто с оператором ?
function сложноеВычисление(x: int) -> Result<int, string> {
let результат1 = безопасноеДеление(x, 2)?
let результат2 = безопасноеДеление(результат1, 3)?
Ok(результат2 * 10)
}defer
function записатьЛог(сообщение: string) -> Result<(), string> {
let файл = open("app.log")?
defer { файл.close() }
файл.write(сообщение)?
return Ok(())
}Каноническая обработка ошибок Topaz сочетает
Result+?сdefer. Формы с ключевым словомtryв Topaz v5.2 нет. Постфиксныйexpr?является каноническим написанием распространения.
Продвинутые возможности
Частичное применение через лямбды
Плейсхолдер _ принадлежит правым сторонам конвейеров; вне конвейера
частичное применение записывается лямбдой.
let добавить10 = x => добавить(10, x)
let результат = map([1, 2, 3], добавить10) // [11, 12, 13]Опциональная безопасная цепочка (?.)
Используйте ?. только когда слева Option<T> или объединение с null (например, T | null). Оператор сводится к Option.map/flatMap и выполняется слева направо с коротким замыканием.
let user: Option<{ name: string, profile: Option<{ city: string }> }> =
Some({ name: "Ann", profile: Some({ city: "Seoul" }) })
let nameOpt = user?.name
let cityOpt = user?.profile?.city
// С комбинированием с ?? для значений по умолчанию
let city = user?.profile?.city ?? "Unknown"
// Это не общий объектный чейнинг — для обычных значений используйте `.`
let plain = { name: "Jo" }
let ok = plain.nameМакросы
Макросы не специфицированы в Topaz v5.2.
Практические примеры
Вызов веб-API
let данныеПользователя = fetch("https://api.example.com/users/1")
|> json()
|> (данные => {
имя: данные.name,
email: данные.email,
датаРегистрации: Date.parse(данные.created_at)
})Конвейер обработки данных
let результатАнализа = CSV.прочитать("продажи.csv")
|> фильтр(строка => строка.выручка > 1000000)
|> группировать(строка => строка.регион)
|> агрегировать(группа => {
регион: группа.ключ,
общаяВыручка: группа.значения.сумма(строка => строка.выручка),
средняяВыручка: группа.значения.среднее(строка => строка.выручка)
})
|> сортировать(поУбыванию: строка => строка.общаяВыручка)Следующие шаги
Теперь вы освоили базовый синтаксис Топаза! Что изучать дальше:
- Типы данных - Глубокое погружение в систему типов
- Функции и замыкания - Освоение функционального программирования
- Обработка ошибок - Написание безопасного, надёжного кода
Поверхность мала — эти страницы покрывают её целиком.