Руководство по описанию метамоделей и моделей в Lean
Введение и терминология
При моделировании систем часто используют многоуровневую архитектуру метамоделирования (например, подход OMG MOF с уровнями M3, M2, M1 и M0). В контексте Lean нам потребуется интерпретировать эти понятия особым образом:
Мир – отдельное пространство или контекст моделирования. Это аналог независимой вселенной объектов и понятий. Каждый «мир» можно понимать как отдельный DSL (Domain-Specific Language), со своими сущностями и правилами. В Lean такой мир мы будем оформлять в виде отдельного namespace (пространства имён) с собственными определениями.
DSL (предметно-ориентированный язык) – набор специфических понятий и правил для моделирования определённой предметной области. В Lean под DSL мы будем понимать совокупность типов и функций, определённых для этого мира. Например, DSL для ER-диаграмм будет содержать понятия сущности, атрибута, отношения и т.д.
Уровень моделирования – слой абстракции в многоуровневой архитектуре. Например, уровень M3 – мета-метамодель (описание средств описания моделей), M2 – метамодель (описание конкретного языка моделирования), M1 – сама модель предметной области, M0 – реальные данные/объекты. В терминах MOF: M3 содержит язык MOF, на M2 находятся метамодели (например, метамодель UML), на M1 – сами модели (например, модель на UML), а M0 – объекты реального мира
ru.wikipedia.org
. В Lean эти уровни будут отражаться в коде так: M2-уровень опишем через типы и структуры, M1 – через экземпляры этих типов (данные), а M3 при необходимости – через ещё более абстрактные конструкции (например, структуру, описывающую элементы метамоделей).
 Рисунок: Классическая четырёхуровневая архитектура метамоделирования OMG. На уровне M3 определяется метаязык (например, MOF 2.0), на уровне M2 – метамодель (например, UML 2.0), на уровне M1 – пользовательская модель (конкретная диаграмма, здесь «Video» как класс с атрибутом title), на уровне M0 – объекты реального мира (конкретный экземпляр, фильм Casablanca). Связи «instanceOf» показывают отношение экземплярности между уровнями.
В среде Lean мы можем отразить эту иерархию, определяя на каждом уровне соответствующие конструкции. Важное различие: Lean сам по себе является языком для формального описания и доказательства, поэтому «уровни» моделирования не встроены, а должны явно моделироваться средствами самого Lean.
Описание миров (пространств) в Lean
Lean поддерживает иерархические имена и пространства имен для структурирования определений
leanprover.github.io
. Мы можем воспользоваться этим, чтобы каждый DSL (мир) оформить как отдельный namespace. Внутри него будут определены основные элементы модели – сущности, атрибуты, связи и т.п. Такое разделение позволяет логически изолировать разные «миры» и даже использовать одинаковые названия для понятий в разных контекстах, не вызывая конфликтов.
Например, определим простой язык ER (Entity-Relationship) на уровне M2. Создадим пространство ER
и опишем в нём ключевые элементы:
$$
namespace ER
/-- Атрибут сущности: имя и тип (упрощённо тип представлен строкой). -/
structure Attribute where
name : String
type : String
/-- Сущность (сущность может иметь список атрибутов). -/
structure Entity where
name : String
attributes : List Attribute
/-- Отношение между двумя сущностями (двунаправленная связь). -/
structure Relationship where
name : String
source : String -- имя исходной сущности
target : String -- имя целевой сущности
/-- Модель на языке ER: набор сущностей и набор отношений между ними. -/
structure Model where
entities : List Entity
relationships: List Relationship
end ER
$$
В этом коде мы ввели четыре основных понятия ER-диаграмм: Attribute (атрибут), Entity (сущность), Relationship (отношение) и Model (модель, объединяющая сущности и отношения). Благодаря namespace ER
все эти определения собраны под префиксом ER.
(например, ER.Entity
, ER.Model
), что означает принадлежность миру ER.
Обратите внимание на организацию:
Мы используем structure
для определения понятий. Это задаёт структуру данных (поля) для каждой сущности модели. Например, ER.Entity
имеет поля name
и список attributes
.
Поля могут ссылаться на другие структуры: так, ER.Entity
содержит список Attribute
. В Lean нужно определять структуры в правильном порядке: мы описали Attribute
прежде, чем использовать его внутри Entity
.
Отношение Relationship
ссылается на сущности через имена (source
и target
– имена сущностей). Альтернативой могло бы быть хранение прямых ссылок или идентификаторов, но для простоты и наглядности мы храним имена и будем проверять их корректность отдельно.
Такое описание задаёт абстрактный синтаксис DSL: какие элементы существуют и как они вложены друг в друга. На этом этапе мы лишь определили типы, но не задавали никаких ограничений (например, мы не запрещаем повторять имя сущности или ссылаться на несуществующую сущность в Relationship
– эти ограничения можно наложить отдельными правилами или проверить отдельными функциями/теоремами).
Отношение метамодельности и соответствие уровней
Теперь разберёмся, как одна модель описывается на языке другой модели, то есть что значит отношение метамодельности (instance-of) в наших терминах, и чем оно отличается от наследования.
Модель и метамодель. Когда мы говорим, что модель соответствует метамодели (например, ER-модель на уровне M1 соответствует метамодели ER на уровне M2), мы подразумеваем, что все элементы модели являются экземплярами (конкретными реализациями) определений из метамодели. В нашем Lean-представлении это буквально означает, что конкретная модель (M1) будет выражена как значение типа, определённого на M2. Например, если на уровне M2 определена структура ER.Entity
, то на уровне M1 конкретная сущность (скажем, клиент Customer) будет просто значением типа ER.Entity
(с определёнными полями name = "Customer"
, своими атрибутами и т.д.). Иначе говоря, экземплярность (интерпретация) модели M1 в метамодели M2 реализуется естественным образом: через конструирование значений определённых на M2 типов.
Наследование vs. интерпретация. Важно не путать отношение «экземпляр метамодели» с обычным наследованием (подклассами). Наследование – это отношение внутри одного уровня (например, одна сущность M2 может быть определена как расширение другой, т.е. как подкласс, добавляющий поля). В Lean наследование метamodelных элементов можно моделировать через механизм расширения структур. Например, если бы мы хотели сказать, что сущность Person
– предок для Customer
в языке ER, мы могли бы в Lean сделать structure Customer extends Person ...
на уровне M2 (это был бы элемент метамодели, унаследованный от другого элемента метамодели). Но когда речь идёт о том, что Customer
на уровне M1 является экземпляром (примером) общего понятия Entity
на уровне M2 – это не наследование в терминах Lean, а именно интерпретация: конкретное значение соответствует общему описанию. Такое соответствие нельзя «вшить» через extends, его нужно задавать явно.
В Lean соответствие «метамодель–модель» чаще задаётся данными или отображениями. Например, можно ввести предикат или функцию, которая проверяет/показывает, что данное значение типа M1 соответствует определённой конструкции на M2. Но в простейшем случае достаточно осознавать, что конкретная модель M1 – это просто данные (значения) типов, описанных на M2.
Отображение DSL в DSL. Помимо отношения «экземплярности» (model conforms to metamodel), часто возникает задача сопоставить один DSL другому DSL (например, преобразовать ER-модель в UML-модель). Такое сопоставление мы будем называть маппингом или трансформацией. В Lean маппинг между DSL обычно реализуется функцией, которая берёт объект (модель) из одного namespace (мира) и строит соответствующий объект в другом. Мы рассмотрим пример такой трансформации (ER → UML) ниже. При этом стоит отметить, что если разные DSL описаны на общем метаязыке, можно формально определить корректность маппинга и доказать сохранение свойств (например, доказать теорему о том, что трансформация не теряет информацию).
Таким образом:
Наследование в Lean-контексте мы используем для отношений внутри одной метамодели (например, специализированные сущности, унаследованные от более общих).
Интерпретация (экземплярность) – это просто создание значений, соответствующих описанным типам (например, объявление конкретной модели как значения типа ER.Model
).
Отображение (маппинг) между DSL – функция, переводящая модель одного типа в модель другого типа, обычно на том же уровне (M1→M1 или M2→M2), но в разных «мирах».
Примеры: описания моделей на уровнях M3, M2, M1
Рассмотрим примеры на каждом уровне абстракции, чтобы закрепить понятия.
Уровень M3: простая мета-метамодель (MOF)
На уровне M3 находится мета-метамодель – язык, которым описываются метамодели. Полноценный MOF довольно сложен, но для наших целей создадим упрощённую версию. Пусть мета-метамодель оперирует понятием класса метамодели с определёнными свойствами. В терминах Lean определим структуру для описания метаклассов:
$$
namespace MOF
/-- Метакласс (элемент метамодели): имеет имя и набор характеристик (фич).
Упрощённо, список features
содержит имена свойств/атрибутов. -/
structure MetaClass where
name : String
features : List String
/-- Метамодель: набор метаклассов. -/
structure MetaModel where
classes : List MetaClass
end MOF
$$
Здесь MOF.MetaClass
– это некая абстрактная сущность уровня M3, описывающая структуру элементов языка моделирования. В более сложной версии можно было бы указать типы каждой фичи, возможные связи между метаклассами, но для простоты ограничимся именами характеристик. MOF.MetaModel
тогда представляет собой совокупность таких метаклассов – то есть описание целого DSL.
Теперь с помощью этой M3-модели мы можем описать любую метамодель (M2). Например, опишем метамодель ER (наш DSL) как экземпляр MOF.MetaModel
. Укажем, какие метаклассы она содержит:
$$
open MOF -- чтобы использовать MetaClass без префикса
/-- Описание метамодели ER на уровне M3 (через MOF). -/
def ER_metamodel : MetaModel :=
{ classes := [
{ name := "Entity", features := ["attributes"] },
{ name := "Attribute", features := [] },
{ name := "Relationship", features := ["source", "target"] }
] }
$$
Мы получили объект ER_metamodel
типа MOF.MetaModel
. Он содержит три метакласса:
Entity с одной характеристикой "attributes"
(то есть у сущности в ER есть свойство-список атрибутов).
Attribute без дополнительных свойств (атрибут – примитивный элемент, у него нет вложенных структур в нашей модели).
Relationship с характеристиками "source"
и "target"
(отношение имеет указание на исходную и целевую сущности).
По сути, мы описали на языке MOF тот же самый язык ER, что и в namespace ER
. Здесь важно понять: мы проделали это явно лишь для демонстрации соответствия M2→M3. В реальных проектах, как правило, метамодель разрабатывают либо сразу в виде Lean-структур (как мы сделали в namespace ER
), либо в виде такой MOF-модели и затем генерируют Lean-структуры. Мы же показали, что наши Lean-определения ER.Entity
, ER.Attribute
, ER.Relationship
соответствуют описанию ER_metamodel
на M3-уровне (каждый из этих типов – реализация соответствующего метакласса).
Уровень M2: метамодель ER (пример DSL)
Мы уже ввели основные конструкции уровня M2 для ER-диаграмм в разделе про «миры». Метамодель ER включает сущности, атрибуты и отношения. Формально, метамодель – это совокупность правил, описывающих что такое корректная ER-модель. В нашем Lean-представлении эти правила частично зашиты в сами типы (например, у сущности обязательно есть имя, у отношения есть source и target), а частично могут быть заданы отдельными ограничениями (например, что каждый source
и target
должны указывать на существующую сущность, или что имена сущностей уникальны). Пока мы не формализовали ограничения, но структуру данных задали.
Таким образом, метамодель ER в Lean – это просто набор определений (как код выше в namespace ER
). Мы можем думать о ER.Model
как об аналоге синтаксиса модели: любая конкретная модель будет выражена как значение этого типа, и не каждое произвольное значение будет смысловым (например, мы можем написать бессмысленный ER.Model
с двумя сущностями с одинаковым именем – синтаксически Lean это примет, но мы могли бы ввести дополнительный критерий корректности, см. ниже).
Уровень M1: конкретная модель (пример Customer-Order)
Теперь создадим конкретную модель на основе нашей метамодели ER. Пусть это будет упрощённая модель предметной области, например, система заказов: есть сущность Customer (клиент) и Product (товар), и отношение Purchase (покупка) между ними.
В Lean мы опишем это, создавая значения структур ER.Entity
, ER.Attribute
и т.д., а затем соберём их в ER.Model
:
$$
open ER -- раскроем пространство ER, чтобы не писать префиксы
-- Определим атрибуты для сущностей:
def customerNameAttr : Attribute := { name := "name", type := "String" }
def customerAgeAttr : Attribute := { name := "age", type := "Int" }
def productNameAttr : Attribute := { name := "name", type := "String" }
def productPriceAttr : Attribute := { name := "price", type := "Float" }
-- Определим сущности с их атрибутами:
def Customer : Entity :=
{ name := "Customer", attributes := [customerNameAttr, customerAgeAttr] }
def Product : Entity :=
{ name := "Product", attributes := [productNameAttr, productPriceAttr] }
-- Определим отношение между Customer и Product:
def PurchaseRel : Relationship :=
{ name := "purchases", source := "Customer", target := "Product" }
-- Соберём полную модель:
def OrderModel : Model :=
{ entities := [Customer, Product],
relationships := [PurchaseRel] }
$$
Мы получили OrderModel
типа ER.Model
. Это и есть наша конкретная модель (уровень M1) – выраженная на языке ER.
Несколько замечаний:
Мы явно создали атрибуты, затем сущности, потом отношения, и всё это составило модель. Здесь source
и target
отношения PurchaseRel
заданы строками "Customer"
и "Product"
– они должны совпадать с именами определённых сущностей. В данном случае мы не делали проверку, но визуально видим, что такие сущности есть.
При желании, можно было бы оптимизировать описание (например, написать сразу списки без промежуточных переменных), но в учебных целях мы развернули всё явно.
Эта модель соответствует нашей метамодели ER: OrderModel
– значение типа ER.Model
, поля которого заполнены в соответствии с ожидаемой структурой (список Entity
и список Relationship
). То есть Lean на уровне типов уже гарантирует, что каждая сущность имеет имя и список атрибутов, каждый атрибут имеет имя и тип и т.д. – иначе мы бы не смогли построить значение типа ER.Model
. Соответствие же более тонких свойств (как уникальность имён, корректность ссылок) пока не проверено, но мы можем проверить или доказать их отдельно.
Описание соответствия между мирами (маппинг: ER → UML)
Очень часто модели нужно переводить из одного представления в другое. Предположим, мы хотим отобразить нашу ER-модель в эквивалентную UML-диаграмму классов. Известно, что ER-диаграмму можно преобразовать в UML-класс диаграмму почти напрямую: «сущность -> класс», «атрибут -> атрибут», «отношение -> ассоциация»
softwareengineering.stackexchange.com
. В UML классы могут иметь методы и наследование, но если их игнорировать, класс-диаграмма по сути охватывает те же понятия, что и ER.
Для начала опишем небольшой фрагмент UML (упрощённо, только то, что нам нужно для соответствия с ER). Создадим namespace UML
с определениями классов и ассоциаций:
$$
namespace UML
/-- Атрибут класса UML: имя и тип (строка). -/
structure Attribute where
name : String
type : String
/-- Класс UML: имя и набор атрибутов. -/
structure Class where
name : String
attributes : List Attribute
/-- Ассоциация между двумя классами (для простоты без ролей и кратности). -/
structure Association where
class1 : String
class2 : String
/-- Модель UML: список классов и список ассоциаций. -/
structure Model where
classes : List Class
associations: List Association
end UML
$$
Эта метамодель UML (частичная) очень похожа по структуре на ER, но в отдельном пространстве имён. Мы намеренно назвали структуры аналогично (Attribute, Class vs Entity, etc.), подчеркнув сходство, но благодаря namespace Lean не конфликтует с ранее определёнными ER.Attribute
и др.
Теперь реализуем функцию, которая берёт ER-модель и строит эквивалентную UML-модель. По сути, нужно: для каждой ER-сущности сделать UML-класс с тем же именем и такими же атрибутами, а для каждого ER-отношения – UML-ассоциацию, связывающую соответствующие классы.
$$
open ER UML -- откроем оба пространства для удобства
/-- Преобразование ER-модели в UML-модель. -/
def transformERtoUML (m : ER.Model) : UML.Model :=
{ classes := m.entities.map (fun e =>
{ name := e.name,
attributes := e.attributes.map (fun a =>
{ name := a.name, type := a.type } ) } ),
associations := m.relationships.map (fun r =>
{ class1 := r.source, class2 := r.target } ) }
$$
Здесь мы использовали функцию высшего порядка List.map
для преобразования списков:
m.entities.map (...)
– проходится по всем сущностям ER и для каждой возвращает UML.Class
. В теле лямбды мы создаём новый класс с тем же именем e.name
, а список атрибутов строим, отобразив каждый ER-атрибут a
в новый UML.Attribute
с таким же именем и типом.
Аналогично, для каждого отношения r
из m.relationships
создаётся UML.Association
с именами классов r.source
и r.target
. Предполагается, что в ER-модели source
и target
были валидными именами сущностей – тогда в результирующей UML-модели эти строки совпадут с именами соответствующих классов (т.к. мы все сущности перенесли).
Важно, что наш transformERtoUML
не проверяет корректность модели – он предполагает, что исходная ER-модель осмысленная. Например, если в ER-связи указана несуществующая сущность, функция всё равно создаст ассоциацию с ссылкой на класс, которого нет (что даст некорректную UML-модель). Поэтому на практике, перед трансформацией, нам стоит убедиться в целостности ER-модели. Мы покажем далее, как это можно формализовать и даже доказать корректность трансформации.
Теоремы и доказательства соответствия
Одно из преимуществ использования Lean – возможность строго доказать свойства моделей и трансформаций. Для этого мы формализуем ограничения и требования в виде утверждений (Prop) и затем пытаемся доказать их, используя встроенный механизм теорем и тактиков доказательства.
Формализация свойств модели. Вернёмся к примеру ER: естественные ограничения – уникальность имён сущностей, существование сущностей, упомянутых в отношениях, и т.д. Сформулируем, к примеру, свойство «все ссылки в отношениях валидны», то есть каждая связь Relationship
ссылается на реально существующие сущности:
$$
open ER
/-- Предикат корректности ER-модели: каждый Relationship ссылается на существующие Entity. -/
def wellFormedER (m : Model) : Prop :=
∀ r ∈ m.relationships, (∃ e ∈ m.entities, e.name = r.source) ∧ (∃ e ∈ m.entities, e.name = r.target)
$$
Здесь мы говорим: для любой связи r
в списке relationships
модели m
существуют сущности в списке entities
с именами, совпадающими с r.source
и r.target
. Это определение в Lean имеет тип Prop
, то есть логическое утверждение.
Аналогично можно определить свойство корректности UML-модели (ассоциации ссылаются на существующие классы):
$$
open UML
/-- Предикат корректности UML-модели: каждая Association ссылается на существующие Class. -/
def wellFormedUML (m : Model) : Prop :=
∀ a ∈ m.associations, (∃ c ∈ m.classes, c.name = a.class1) ∧ (∃ c ∈ m.classes, c.name = a.class2)
$$
Теперь мы можем доказать теорему, что наша трансформация сохраняет корректность: если ER-модель была корректной по вышеописанному свойству, то и полученная UML-модель корректна.
$$
theorem transform_preserves_wellFormed (m : ER.Model) (h : ER.wellFormedER m) :
UML.wellFormedUML (transformERtoUML m) :=
begin
-- Пусть a - произвольная ассоциация в результате трансформации:
intros a ha,
-- По определению transformERtoUML, каждая ассоциация возникла из некоторого отношения r ∈ m.relationships.
rcases List.mem_map.1 ha with ⟨r, hr, rfl⟩,
-- По предположению h: r.source и r.target соответствуют существующим сущностям e1, e2.
cases h r hr with ⟨e1, he1, hs⟩ ⟨e2, he2, ht⟩,
-- Так как transformERtoUML переносит каждую сущность в класс, найдём классы с именами e1.name и e2.name:
refine ⟨⟨{ name := e1.name, attributes := _ }, _, hs⟩, ⟨{ name := e2.name, attributes := _ }, _, ht⟩⟩;
-- Завершение доказательства: классы с такими именами действительно присутствуют в UML.модели
apply List.mem_map_of_mem _ he1 <|> apply List.mem_map_of_mem _ he2,
end
$$
(Вышеприведённое доказательство иллюстрирует структуру рассуждений: мы берём произвольную ассоциацию a
из результата, находим соответствующее отношение r
в исходной модели через свойство mem_map
, затем применяем предположение h
о корректности ER-модели к r
и получаем существование сущностей e1
, e2
с нужными именами. После этого, зная как построены классы в transformERtoUML
, мы заключаем, что классы с именами e1.name
и e2.name
присутствуют в результирующей UML-модели. Доказательство завершается применением лемм о принадлежности элементов списку после map
.)
Таким образом, мы формально удостоверились, что transformERtoUML
не нарушит ссылочную целостность: валидная ER-модель перейдёт в валидную UML-модель.
Lean позволяет не только проверить конкретные случаи, но и доказать общие свойства для всех моделей. Например, мы могли бы доказать отдельную теорему о том, что в любой ER-модели имена сущностей уникальны (предварительно сформулировав это свойство). Или доказать инвариант, что при любой модели, прошедшей через определённую трансформацию, выполняется какое-то условие. Всё это достигается комбинацией правильной формализации (через def
свойств) и применения механизма теорем (theorem/lemma
и тактик для доказательств).
Отметим, что если мы хотим проверить свойство для конкретной модели (а не для всех абстрактных моделей), мы можем использовать команду вычисления или простой вывод. Например, убедимся, что наш пример OrderModel
удовлетворяет свойству целостности связей:
$$
#eval ER.wellFormedER OrderModel -- вернёт true или false (если сделать wellFormedER bool-выражением)
-- Или доказать как теорему фактическую выполнимость:
example : ER.wellFormedER OrderModel :=
by simp [OrderModel, Customer, Product, PurchaseRel]
$$
Второй способ (example
) позволяет Lean автоматически проверить простое утверждение на основе подстановки наших конкретных данных (simp
подставит определения и убедится, что условие выполнено). Это не ручное доказательство, а скорее автоматическая проверка конкретного случая, но она становится доказательством того, что OrderModel корректна.
В итоге, теоремы и доказательства служат для того, чтобы убедиться в соответствии между мирами: например, модель соответствует своей метамодели, трансформация сохраняет свойства, две модели эквивалентны друг другу и т.д. Lean, как теоремный доказчик, гарантирует, что такие утверждения проверены формально.
Типовые операции с моделями в Lean
Когда мы описали модели и метамодели, следующие шаги обычно включают различные операции над ними. В среде Lean многие такие операции могут быть реализованы и изучены:
Трансформация моделей. Как мы показали на примере transformERtoUML
, можно написать функцию, преобразующую модель одного DSL в модель другого DSL. Lean позволяет затем вычислять результат (используя #eval
или встраивая в код), а также доказывать свойства о трансформации. Т.е. Lean одновременно играет роль исполняемого языка (для прототипирования преобразований) и системы доказательств (для верификации этих преобразований).
Проверка свойств (валидация). Для заданной модели можем автоматически проверять определённые условия. Например, написать функцию checkModel : ER.Model -> Bool
, которая пробегается по спискам и возвращает true
, если модель соблюдает все ограничения (никаких дубликатов имён, корректные ссылки, и пр.). Эту функцию можно исполнить на конкретных данных. Кроме того, можно формулировать свойства как логические (Prop
) и использовать Lean для доказательства, что checkModel m = true
эквивалентно выполнению определённых условий, или что для конкретной модели m
эта функция возвращает истину (то есть модель корректна).
Доказательство инвариантов. Можно доказывать, что некоторые свойства неизменны при определённых операциях. Например, если у нас есть функция трансформации или обновления модели, мы можем показать, что определённый инвариант (скажем, целостность связей или уникальность имён) сохраняется. Инварианты могут относиться как к отдельной модели, так и к паре метамодель-модель. Например, инвариант соответствия: «модель всегда остаётся в пределах своей метамодели». Поскольку Lean позволяет выполнять индуктивные доказательства по структуре данных, мы можем доказать, например, что для всех моделей m
выполнимо wellFormedER m -> wellFormedER (какая-то операция m)
, т.е. операция не нарушает корректность.
Типовые операции не ограничены перечисленным: можно моделировать слияние моделей, сравнение, рефакторинг (например, преобразование наследования), генерацию кода по модели и многое другое. Все эти задачи выигрывают от того, что Lean обеспечивает строгую типизацию и возможность доказательства: вероятность сделать ошибку в определении или упустить какой-то случай значительно снижается.
Практические советы по организации кода
Наконец, несколько практических рекомендаций для специалистов, начинающих экспериментировать с описанием моделей в Lean:
Разграничение миров: Каждый DSL (модельный язык) оформляйте в своём namespace
или даже отдельном файле. Это предотвращает конфликт имён и явно показывает, где заканчивается одна «вселенная» и начинается другая. Например, мы создали ER
и UML
как отдельные пространства. В крупных проектах можно файлы назвать соответственно: ER.lean
, UML.lean
, MOF.lean
и т.д., и импортировать их по мере необходимости (import ER
).
Говорящие имена и соглашения: Следуйте принятым в Lean соглашениям именования. Обычно для типов и структур используют PascalCase (например, Entity
, Relationship
), для функций и свойств – camelCase или snake_case (wellFormedER
, transformERtoUML
). Имена должны отражать суть: пусть PurchaseRel
явно указывает на отношение Purchase, а не называется просто r1
. Это облегчает чтение и сопровождение. Если в разных пространствах встречаются сходные понятия, называйте их одинаково (как Attribute
у ER и UML) – так будет проще сопоставлять при чтении кода. Lean позволит различать их по префиксу (ER.Attribute
vs UML.Attribute
) при необходимости.
Логическое разделение уровней: Старайтесь отделять метамодельный код от кода конкретных моделей и от кода трансформаций. Например, определения структур (M2) держите отдельно, описание конкретной модели (M1 пример) – отдельно, функции трансформации – отдельно, доказательства – тоже можно вынести в отдельный файл или секцию. Это поможет не запутаться в том, что является общим описанием языка, а что – частной реализацией или процессом.
Документирование соответствий: Рекомендуется явно документировать, какая часть кода относится к какому уровню. Комментарии вроде -- M2: define meta-model X
или -- M1 example model
помогут читателям (и вам самим спустя время) быстрее ориентироваться. Также, если некоторые Lean-структуры соответствуют понятиям из методологий (например, ER.Attribute
соответствует атрибуту ER-диаграммы), можно упомянуть это в docstring к структуре.
Постепенное усложнение: Начинайте с простых моделей (как мы сделали) и проверяйте всё на небольших примерах. Lean позволяет интерактивно проверять типы и выводы (#check
, #eval
, использование среды VS Code с Lean-плагином). Это особенно полезно, чтобы убедиться, что вы правильно задали структуры и функции. По мере уверенности можно добавлять ограничения, инварианты, более сложные связи (например, унаследованные сущности, или связи с ролями).
Использование механизма доказательств: Когда вводите ограничения (вроде wellFormedER
), попробуйте сразу доказать для них хотя бы очевидные леммы на простых моделях. Это послужит двойной проверкой корректности ваших определений. Даже если доказательство звучит тривиально, попытка его провести может выявить, что вы, например, забыли какой-то случай в определении или выбрали неудобную структуру данных.
Повторное использование и модули: Если вы описали что-то на уровне M3 (например, тот же упрощённый MOF), старайтесь использовать это для разных метамоделей, чтобы не дублировать идеи. Lean позволяет параметризовать определения и даже использовать typeclass-механизм для некоторых общих свойств. Однако на первых порах, возможно, проще каждую метамодель описывать с нуля, чтобы лучше прочувствовать процесс. В дальнейшем, увидев общие паттерны, вы сможете вынести их в библиотеку.
Следуя этим рекомендациям, вы создадите понятный и проверяемый набор моделей. Lean будет служить вам одновременно и языком описания (как UML-диаграмма, только в текстовой форме), и инструментом для исполнения (проверки, трансформации) и верификации (формального доказательства свойств). Это мощное сочетание, которое сильно снижает риск неоднозначностей и ошибок по сравнению с неформальными схемами. Начните с малого: опишите пару простых миров, попробуйте руками собрать модель, написать к ней пару проверок – и постепенно выработается свой стиль и набор удобных приёмов моделирования в Lean.