Давай построим завершённый минимальный кейс QVTd-подобного DSL в Lean — на примере соответствия UML.Class ↔ RDBMS.Table
.
Мы сделаем всё поэтапно, чтобы получилось читаемо и законченное:
Сначала — match
и проверка соответствия;
Потом — generate
(автоматическая генерация одной стороны из другой);
Затем — where
(дополнительные ограничения);
И в конце — небольшой engine
, который может находить, проверять и строить соответствия по правилам.
✅ 1. Обновим структуру CorrespondenceRule
Теперь правило будет включать не только match
, но и generate
— как построить недостающий элемент:
$$
structure CorrespondenceRule (A B : Type) where
name : String
description : String
direction : String := "↔"
match : A → B → Prop
generateA? : Option (B → A) := none -- сгенерировать A по B
generateB? : Option (A → B) := none -- сгенерировать B по A
$$
📘 2. Объявим модели UML и RDBMS (тот же код)
$$
namespace UML
structure Attribute where
name : String
type_ : String
structure Class where
name : String
attributes : List Attribute
end UML
namespace RDBMS
structure Column where
name : String
type_ : String
structure Table where
name : String
columns : List Column
end RDBMS
$$
🧠 3. Пример соответствия: UML.Class ↔ RDBMS.Table
$$
open UML RDBMS
def class_table_rule : CorrespondenceRule UML.Class RDBMS.Table :=
{
name := "ClassToTable",
description := "UML.Class соответствует Table, если имя и атрибуты совпадают",
direction := "↔",
match := λ c t =>
c.name = t.name ∧
c.attributes.length = t.columns.length ∧
∀ i, i < c.attributes.length →
(c.attributes.get! i).name = (t.columns.get! i).name ∧
(c.attributes.get! i).type_ = (t.columns.get! i).type,
generateB? := some (λ c => {
name := c.name,
columns := c.attributes.map (λ a => { name := a.name, type := a.type_ })
}),
generateA? := some (λ t => {
name := t.name,
attributes := t.columns.map (λ col => { name := col.name, type_ := col.type_ })
})
}
$$
⚙️ 4. Проверка и генерация
$$
def checkCorrespondence {A B : Type}
(r : CorrespondenceRule A B) (a : A) (b : B) : Bool :=
if h : r.match a b then true else false
def generateB {A B : Type}
(r : CorrespondenceRule A B) (a : A) : Option B :=
match r.generateB? with
| some f => some (f a)
| none => none
def generateA {A B : Type}
(r : CorrespondenceRule A B) (b : B) : Option A :=
match r.generateA? with
| some f => some (f b)
| none => none
$$
🧪 5. Пример: класс и таблица
$$
leanКопироватьРедактироватьdef classCustomer : UML.Class :=
{ name := "Customer",
attributes := [
{ name := "id", type_ := "Int" },
{ name := "name", type_ := "String" }
] }
def tableCustomer : RDBMS.Table :=
{ name := "Customer",
columns := [
{ name := "id", type_ := "Int" },
{ name := "name", type_ := "String" }
] }
$$
✅ 6. Проверим работу
$$
#eval checkCorrespondence class_table_rule classCustomer tableCustomer
-- должно вернуть true
#eval generateB class_table_rule classCustomer
-- создаст Table из Class
#eval generateA class_table_rule tableCustomer
-- создаст Class из Table
$$
📌 Что дальше?
Теперь у нас есть:
✅ правило соответствия Class ↔ Table
;
✅ автоматическое построение одной стороны по другой;
✅ проверка соответствия;
✅ вся логика работает на типах.
🔜 Следующий шаг (предлагаю):
Добавим поддержку where
: логика, зависящая от контекста или условий;
Создадим engine
— чтобы можно было прогнать List
правил на List
объектов и получить маппинг;
Сделаем вывод trace
— какие пары были сопоставлены или сгенерированы.
Хочешь, чтобы я сейчас добавил where
и показал, как его использовать, например, чтобы фильтровать классы с пустыми атрибутами?
4o
Вы сказали:
давай перейдем к предложенным тобой шагам
ChatGPT сказал:
Отлично! Двигаемся дальше:
📦 Добавим поддержку where
и базовый движок match+generate+trace
, чтобы наш мини-QVTd DSL в Lean стал реально похож на рабочий прототип.
🔧 Шаг 1. Расширим CorrespondenceRule
: where
и trace
Теперь в каждое правило можно встроить:
where
— дополнительные ограничения после match
;
trace
— произвольное строковое описание, которое можно логировать/выводить;
id
или tag
, если захотим трекать соответствия в списках (опционально).
$$
leanКопироватьРедактироватьstructure CorrespondenceRule (A B : Type) where
name : String
description : String
direction : String := "↔"
match : A → B → Prop
where_clause : A → B → Prop := λ _ _ => True
generateA? : Option (B → A) := none
generateB? : Option (A → B) := none
$$
⚙️ Шаг 2. Обновим функции checkCorrespondence
, generate
$$
leanКопироватьРедактироватьdef checkCorrespondence {A B : Type}
(r : CorrespondenceRule A B) (a : A) (b : B) : Bool :=
if r.match a b ∧ r.where_clause a b then true else false
def generateB {A B : Type}
(r : CorrespondenceRule A B) (a : A) : Option B :=
match r.generateB? with
| some f => some (f a)
| none => none
def generateA {A B : Type}
(r : CorrespondenceRule A B) (b : B) : Option A :=
match r.generateA? with
| some f => some (f b)
| none => none
$$
🧪 Шаг 3. Обновим наше правило Class ↔ Table
и добавим where
$$
leanКопироватьРедактироватьopen UML RDBMS
def class_table_rule : CorrespondenceRule UML.Class RDBMS.Table :=
{
name := "ClassToTable",
description := "UML.Class соответствует Table, если имя совпадает и атрибуты одинаковы",
direction := "↔",
match := λ c t =>
c.name = t.name ∧
c.attributes.length = t.columns.length,
where_clause := λ c t =>
∀ i, i < c.attributes.length →
(c.attributes.get! i).name = (t.columns.get! i).name ∧
(c.attributes.get! i).type_ = (t.columns.get! i).type,
generateB? := some (λ c => {
name := c.name,
columns := c.attributes.map (λ a => { name := a.name, type := a.type_ })
}),
generateA? := some (λ t => {
name := t.name,
attributes := t.columns.map (λ col => { name := col.name, type_ := col.type_ })
})
}
$$
🔁 Шаг 4. Создаём Engine
, который прогоняет правила
$$
leanКопироватьРедактироватьstructure MatchResult (A B : Type) where
left : A
right : B
rule_name : String
matched : Bool
generated_from : Option String := none -- "left", "right", none
def runRule {A B : Type}
(r : CorrespondenceRule A B)
(a : A) (b : B) : MatchResult A B :=
let matched := checkCorrespondence r a b
{ left := a, right := b, rule_name := r.name, matched := matched }
-- Автоматическая генерация
def runGenerateB {A B : Type}
(r : CorrespondenceRule A B)
(a : A) : Option (MatchResult A B) :=
match r.generateB? with
| some f => some {
left := a,
right := f a,
rule_name := r.name,
matched := true,
generated_from := some "left"
}
| none => none
$$
🧪 Шаг 5. Пример использования
$$
leanКопироватьРедактироватьdef classEmpty : UML.Class :=
{ name := "Empty", attributes := [] }
def tableEmpty : RDBMS.Table :=
{ name := "Empty", columns := [] }
#eval checkCorrespondence class_table_rule classCustomer tableCustomer -- true
#eval checkCorrespondence class_table_rule classEmpty tableEmpty -- true (пока допустим)
#eval runGenerateB class_table_rule classCustomer -- Some MatchResult
$$
🎯 Что теперь умеет DSL:
Декларативно описывать соответствие (match
, where
);
Генерировать недостающую сторону (generateA
, generateB
);
Прогонять правило и получать MatchResult
;
Типобезопасно, читаемо, расширяемо.
🔜 Что можно сделать дальше?
📚 Поддержка списков: сопоставить List UML.Class
и List Table
;
🔍 Улучшенный DSL (с notation
): relation ... when ... where ... generate ...
;
🔗 Связанные правила: сопоставления Association ↔ ForeignKey
;
💾 Выгрузка в JSON или trace формат для отчёта;
✅ Формальные доказательства, что generateB ∘ generateA = id
(рефлексивность соответствия).