RFC-004: Проектирование многопозиционной юнионной привязки каррированных методов
Аннотация
Данный RFC предлагает принципиально новый синтаксис многопозиционной юнионной привязки, позволяющий точно привязать функцию к любому параметру типа, с поддержкой однопозиционной и многопозиционной юнионной привязки, фундаментально решая проблему "кто является вызывающим" в каррированных привязках без введения ключевого слова self.
Мотивация
Зачем нужен этот функционал?
При текущем дизайне языка привязка автономных функций к типам методов сталкивается со следующими проблемами:
- Негибкость позиции вызывающего: традиционная привязка фиксирует
objвobj.method(args)как первый параметр - Сложность привязки нескольких параметров: когда метод должен принимать несколько параметров одного типа, невозможно элегантно выразить это
- Неоднозначность каррированной семантики: при частичном применении сложно различать "к какой позиции привязано"
Цели проектирования: унификация двух парадигм программирования
Данный дизайн направлен на унификацию функциональной и ООП парадигм программирования:
# Функциональная перспектива: явная передача всех параметров
distance(p1, p2)
# ООП перспектива: неявный this
p1.distance(p2)
# Синтаксический сахар [positions] делает обе записи эквивалентными,
# по сути всё является вызовом функции
Point.distance = distance[0] # this привязан к позиции 0Ключевая ценность:
- На нижнем уровне — функция, на верхнем — синтаксис метода
- Без введения ключевого слова
self, сохранение простоты языка - Полная функциональность: вызов метода по сути — передача параметров
[0],[1],[-1]для гибкого контроля позиции привязки this- Синтаксическая унификация: определение функций использует формат
name: (params) -> Return = body
Текущие проблемы
# Проблемы существующего дизайна:
Point: Type = { x: Float, y: Float }
Vector: Type = { x: Float, y: Float, z: Float }
distance: (a: Point, b: Point) -> Float = { ... }
transform: (p: Point, v: Vector) -> Point = { ... }
# Можно привязать только к первому параметру
Point.distance = distance # эквивалентно distance[0]
# p1.distance(p2) → distance(p1, p2) ✓
# Но что если сигнатура transform — transform(Vector, Point)?
# Невозможно выразить семантику p1.transform(v1) → transform(v1, p1)Предложение
Основной дизайн: явное указание позиции
Основное правило: без [n] = без привязки. Point.name = func — это лишь пространство имён, не вызывающее никакой неявной привязки. Чтобы синтаксис вызова . сработал (p.name(args)), необходимо явно указать: Point.name = func[n].
Однопозиционная привязка
# Явная привязка к первой позиции параметра Point (индекс с 0)
Point.distance = distance[0]
p1.distance(p2) # → distance(p1, p2)
# Привязка ко второй позиции параметра Point
Point.compare = distance[1] # привязка ко второму параметру Point
p1.compare(p2) # → distance(p2, p1)Без [n] = без привязки:
# Нет [n] → просто псевдоним пространства имён, без синтаксиса . вызова
Point.distance = distance # только Point.distance(p1, p2)
# p1.distance(p2) ❌ нет привязки
# Фабричная функция естественно допустима, без специальной обработки
create_point: () -> Point = { ... }
Point.create = create_point # Point.create() ✅- Типобезопасность: привязка только при совпадении типов, избежание ошибок
- Гибкий контроль: через
[n]точное управление позицией привязки
Каррированная привязка
Когда количество параметров функции > количества позиций привязки, автоматически генерируется каррированная функция. Привязка всегда явная операция.
Point: Type = { x: Float, y: Float }
# Базовая функция
scale: (p: Point, factor: Float) -> Point = {
return Point(p.x * factor, p.y * factor)
}
# Явная привязка к позиции 0 → каррирование: оставшийся параметр factor предоставляется вызывающим
Point.scale = scale[0]
# Вызов
p1 = Point(2.0, 3.0)
scaled = p1.scale(2.0) # → scale(p1, 2.0)
# Цепочечные вызовы более элегантны
result = Point(2.0, 3.0).scale(2.0) # → Point(4.0, 6.0)Синтаксис позиционного индексирования привязки
Вводится синтаксис [position] для точного управления связью между параметрами функции и типами:
# Синтаксис формата: Type.method = function[positions]
# === Базовая привязка ===
# Однопозиционная привязка
Point.distance = distance[1] # привязка к параметру 1 (индекс с 0)
# Использование: p1.distance(p2) → distance(p2, p1)
# Многопозиционная юнионная привязка (деструктуризация кортежа)
Point.transform = transform[1, 2] # привязка к параметрам 1, 2
# Использование: p1.transform(v1) → transform(v1, p1)
# Исходная сигнатура функции: transform(Point, Vector) → Point
# После привязки: Point.transform(Vector) → PointДетальное определение синтаксиса
Привязка ::= Тип '.' Идентификатор '=' ИмяФункции '[' СписокПозиций ']'
СписокПозиций ::= Позиция (',' Позиция)*
Позиция ::= Целое # Плейсхолдер
| '_' # Пропуск этой позиции (плейсхолдер)
| Целое '..' Целое # Диапазон позиций (расширение в будущем)
ИмяФункции ::= Идентификатор
Тип ::= Идентификатор (ПараметрыТипа)?Встроенные привязки
Привязка может быть записана прямо в теле определения типа, без отдельного оператора привязки:
# Способ 1: прямая привязка в теле определения типа
Point: Type = {
x: Float = 0,
y: Float = 0,
distance = distance[0] # привязка к позиции 0
}
# Способ 2: анонимная функция + позиционная привязка
Point: Type = {
x: Float = 0,
y: Float = 0,
distance: ((a: Point, b: Point) -> Float)[0] = ((a, b) => {
dx = a.x - b.x
dy = a.y - b.y
return (dx * dx + dy * dy).sqrt()
})
}
# Синтаксис: ((params) => body)[position]Семантика каррирования:
- При привязке
distance = distance[0]исходная сигнатура функции:(a: Point, b: Point) -> Float - Сгенерированная сигнатура метода:
b: Point -> Float(позиция 0 заполняется вызывающим)
Примеры использования
# === Полный пример ===
Point: Type = { x: Float, y: Float }
Vector: Type = { x: Float, y: Float, z: Float }
# 1. Базовый расчёт расстояния
distance: (a: Point, b: Point) -> Float = {
dx = a.x - b.x
dy = a.y - b.y
return (dx * dx + dy * dy).sqrt()
}
# Привязка: Point.distance = distance[1]
# Вызов: p1.distance(p2) → distance(p2, p1)
# Но мы хотим p1.distance(p2) → distance(p1, p2), поэтому:
Point.distance = distance[0]
# 2. Операция трансформации (многопозиционная привязка)
transform: (p: Point, v: Vector) -> Point = {
return Point(p.x + v.x, p.y + v.y)
}
# Привязка Point.transform = transform[1]
# Вызов: p.transform(v) → transform(v, p) ❌
# Привязка Point.transform = transform[0]
# Вызов: p.transform(v) → transform(p, v) ✓
# 3. Сложная функция с несколькими параметрами
multiply: (a: Point, s: Float) -> Point = {
return Point(a.x * s, a.y * s)
}
# Привязка только параметра 1 (тип Point), сохранение параметра 3
Point.scale = multiply[0, _]
# Вызов: p.scale(2.0) → multiply(p, 2.0)
# 4. Межтиповая привязка
Circle: Type = { center: Point, radius: Float }
distance: (a: Circle, b: Circle) -> Float = {
return a.center.distance(b.center) - a.radius - b.radius
}
# Привязка метода расстояния к типу Circle
Circle.distance = distance[0, 1]
# Вызов: c1.distance(c2) → distance(c1, c2)Поддержка деструктуризации кортежей
# === Привязка с деструктуризацией кортежа ===
# Функция принимает параметр-кортеж
process_coordinates: (coord: (Float, Float)) -> String = {
return match coord {
(0.0, 0.0) -> "origin"
(x, 0.0) -> "on x-axis at ${x}"
(0.0, y) -> "on y-axis at ${y}"
(x, y) -> "point at (${x}, ${y})"
}
}
Coord: Type = { x: Float, y: Float }
# Автоматическая деструктуризация привязки: Coord -> (Float, Float)
Coord.describe = process_coordinates[1]
# Использование: coord.describe() → process_coordinates((coord.x, coord.y))Привязка с множественными возвращаемыми значениями
# === Привязка с множественными возвращаемыми значениями ===
min_max: (list: List(Int)) -> (Int, Int) = {
min = list.reduce(Int.MAX, (a, b) => if a < b then a else b)
max = list.reduce(Int.MIN, (a, b) => if a > b then a else b)
return (min, max)
}
List.range: (T:Type)->((self: List(T)) -> (T, T)) = min_max[1]
# Использование: (min_val, max_val) = list.range()Детальной дизайн
Реализация компилятора
Правила проверки типов
fn check_binding_type_compatibility(
binding: &Binding,
func: &Function
) -> Result<(), TypeError> {
// 1. Если это автоматический поиск позиции (не указано явно),
// проверить найдено ли совпадение
if binding.positions.is_empty() {
return Err(TypeError::NoMatchingParameter(
binding.type_name.clone(),
func.name.clone()
));
}
// 2. Проверить валидность всех индексов позиций
for pos in &binding.positions {
if *pos >= func.params.len() {
return Err(TypeError::InvalidBindingPosition(*pos));
}
}
// 3. Проверить совместимость типов в позициях привязки
for pos in &binding.positions {
let param_type = &func.params[*pos].type_;
let binding_type = &binding.type_name;
if !isAssignable(binding_type, param_type) {
return Err(TypeError::IncompatibleTypes(
binding_type, param_type
));
}
}
// 4. Проверить соответствие параметров вызова метода оставшимся параметрам
Ok(())
}Поведение во время выполнения
| Сценарий | Синтаксис привязки | Вызов | Преобразование |
|---|---|---|---|
| Без привязки | Point.distance = distance | Point.distance(p1, p2) | distance(p1, p2) |
| Однопозиционная | Point.distance = distance[0] | p1.distance(p2) | distance(p1, p2) |
| Однопозиционная | Point.distance = distance[1] | p1.distance(p2) | distance(p2, p1) |
| Отрицательный индекс | Point.test = func[-1] | p.test(a, b) | func(a, b, p) |
| Многопозиционная (каррирование) | Point.scale = scale[0] | p.scale(2.0) | scale(p, 2.0) |
| Плейсхолдер | Type.method = func[1] | obj.method(arg) | func(arg, obj) |
Пояснения:
- Без привязки:
Point.name = func— просто псевдоним пространства имён, без синтаксиса.вызова [0]: вызывающий привязан к позиции 0 (первый параметр)[1]: вызывающий привязан к позиции 1 (второй параметр)[-1]: вызывающий привязан к последней позиции (отсчёт с конца)
Компромиссы
Преимущества
- Явная привязка:
[n]— единственный механизм привязки, без написания нет привязки, без неявного поведения - Точный контроль: можно привязать к любой позиции параметра, высокая гибкость
- Типобезопасность: полная проверка типов при компиляции, привязка только при совпадении типов
- Компактный синтаксис: синтаксис
[position]интуитивно понятен - Без ключевого слова
self: сохранение простоты языка - Каррирование: естественная поддержка частичного применения и цепочечных вызовов
- ООП-дружелюбие: автоматическое каррирование позволяет ООП-программистам легко мигрировать
Недостатки
- Затраты на обучение: необходимо понимать концепцию позиционных индексов
- Сложность компиляции: разрешение привязок и проверка типов увеличивают сложность компилятора
- Сложность отладки: сообщения об ошибках должны чётко указывать на проблемы с позициями привязки
Альтернативные решения
| Решение | Описание | Почему не выбрано |
|---|---|---|
Ключевое слово self | Введение self в стиле Python/Rust | Нарушает дизайн YaoXiang без неявного self |
| Именованная привязка параметров | Использование именованных параметров func(a=obj) | Требует изменения сигнатуры определения функции, увеличивает сложность |
| Макросистема | Реализация привязки через макросы | Накладные расходы во время выполнения, снижение типобезопасности |
| Перегрузка операторов | Ограничение self в определённых позициях | Синтаксис не унифицирован, семантика запутанная |
Стратегия реализации
Фазы
Фаза 1: Базовая привязка (v0.3)
- Реализация синтаксиса однопозиционной привязки
[n](n начиная с 0, с поддержкой отрицательных чисел) - Базовая проверка типов и генерация кода
- Покрытие юнит-тестами
- Реализация синтаксиса однопозиционной привязки
Фаза 2: Продвинутые возможности (v0.5)
- Поддержка синтаксиса диапазонов
[n..m] - Оптимизация вычисления позиций на этапе компиляции
- Поддержка синтаксиса диапазонов
Зависимости
- Без внешних зависимостей
- Не связано напрямую с RFC-001 (обработка ошибок)
- Может быть реализовано независимо
Риски
- Совместимость с существующим синтаксисом привязки
- Стратегии оптимизации производительности (раскрытие на этапе компиляции vs поиск во время выполнения)
Открытые вопросы
Следующие вопросы уже решены в дизайне, записаны в Приложении A:
Индекс позиции начинается с 0→ Решено: начинается с 0Отрицательные индексы→ Решено: поддерживаютсяПлейсхолдер→ Решено: используется_Синтаксис диапазона→ Решено: реализовать
Оставшиеся открытые вопросы:
- [ ] Совместимость с существующим синтаксисом привязки
- [ ] Стратегии оптимизации производительности (раскрытие на этапе компиляции vs поиск во время выполнения)
Приложения
Приложение A: Запись решений по дизайну
| Решение | Решение | Обоснование |
|---|---|---|
| База индексации | Начиная с 0 | Согласованность с индексацией кортежей/списков параметров |
| Отрицательные индексы | Поддерживаются | Гибкость, отсчёт с конца |
| Плейсхолдер | _ | Компактность, универсальный символ |
| Синтаксис диапазона | Реализовать | Пакетная привязка, например [0..2] |
| Стиль синтаксиса | Инфиксный Type.method = func[positions] | Унификация с RFC-010 |
| Правило привязки | Явная [n] для привязки, без написания нет привязки | Без неявного поведения, определение функции и привязка ортогональны |
| Пространство имён | Type.name — только принадлежность пространству имён, не вызывает привязку | Разделение определения и привязки |
| Синтаксис функций | Параметры в сигнатуре name: (params) -> Return | Унификация с RFC-010 |
Приложение B: Глоссарий
| Термин | Определение |
|---|---|
| Позиция привязки | Индекс позиции в списке параметров функции |
| Юнионная привязка | Привязка типа к нескольким позициям параметров |
| Частичное применение | Предоставление только части параметров, возврат функции для завершения вызова |
| Унифицированный синтаксис | name: (params) -> Return = body, имя параметра объявляется в сигнатуре |
| Функция пространства имён | Type.name синтаксис, функция принадлежит пространству имён Type, без неявной привязки |
| Явная привязка | Type.name = func[n], единственный механизм привязки методов |
