Опять кодогенерация, шаблонизаторы vs AST
Aug. 12th, 2010 11:49 am![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Кодогенератор-шаблонизатор T4 оказался весьма удобной вещью, на которой кодогенераторы пишутся и расширяются просто со страшной скоростью.
И поначалу это гораздо понятнее, чем кодогенератор с использованием AST целевого языка и претти-принтингом оного AST в исходники.
Но есть одно НО.
Результат без подсветки синтаксиса и автоформатирования к поддержке практически непригоден. Исходник представляет собой смесь C# кода, который выполняется и C# кода который генерируется, с разделителями вида <#,<#=,<#+ и #>. Без заточенного под это дело редактора после определенного уровня сложности кодогенератора в этом понять ничего невозможно.
Например, я, упившись крепким чаем, в припадке безумия таки реализовал свою старую идею - генерацию из простой модели иммутабельных сущностей и методов сохранения и загрузки оных сущностей в простой XML, где полям-значениям соответствуют атрибуты, полям-объектам - элементы, полям-спискам(List<>) - элементы с вложенными списками. Т.е. вариант XML, в котором это дело наиболее читабельно глазами. Если не боитесь пауков - можете глянуть на этот мрак.
По мере расширения этого кодогенератора (простые сущности-записи -> сущности с полями-списками -> поддержка enum -> поддержка полей-вложенных объектов -> поддержка memo-полей (строки в которых длинный текст с переносами)) это все понемногу превратилось в жопный ад. 10 кб текста, 300 строк, в которых даже я с трудом ориентируюсь уже.
Использовать это очень просто, расширять дальше - уже сложно.
Кроме того, во всей этой кодогенерации очень бесит необходимость постоянно расширять систему типов. Т.к. один и тот же string в целевой проге - это может быть и коротенькое имя, и текст SQL запроса и RTF-документ. А при показе поля с датой пользователю может быть необходимо использовать culture-invariant формат (например я для служебных данных и для себя пользуюсь везде только им, чтобы не вспоминать "на какой локали я сижу") а может и формат из текущей локали.
В общем, если делать классическими дотнетовскими средствами, как положено, то на каждое поле или проперть в POCO-сущности навешивается еще 5-10 атрибутов, и по всему коду расползается анализ этих атрибутов, т.е. фактически мало того, что мы расширяем систему типов дополнительными описаниями, так еще и всю их обработку делаем в рунтайме, как с какой-то динамической типизацией.
PS: Во, вспомнил еще идею, которая мне насчет T4 в голову пришла.
Частенько в C# не хватает ADT, но их можно сымитировать, сделав базовый класс и унаследовав от него все варианты ADT с нужными полями. Чтобы сделать pattern matching поверх этого, нужно написать в базовом классе метод, в который передается в качестве параметров по одному делегату на каждый вариант ADT и он анализирует текущий тип объекта, вызывая соответствующий делегат, подставляя поля варианта в качестве параметров делегату.
Руками такое писать очевидно влом. Но можно сделать на T4 мелкий шаблончик, который из описания ADT в привычном виде сгенерит все классы, конструктора, прототипы делегатов и метод для pattern matching.
И поначалу это гораздо понятнее, чем кодогенератор с использованием AST целевого языка и претти-принтингом оного AST в исходники.
Но есть одно НО.
Результат без подсветки синтаксиса и автоформатирования к поддержке практически непригоден. Исходник представляет собой смесь C# кода, который выполняется и C# кода который генерируется, с разделителями вида <#,<#=,<#+ и #>. Без заточенного под это дело редактора после определенного уровня сложности кодогенератора в этом понять ничего невозможно.
Например, я, упившись крепким чаем, в припадке безумия таки реализовал свою старую идею - генерацию из простой модели иммутабельных сущностей и методов сохранения и загрузки оных сущностей в простой XML, где полям-значениям соответствуют атрибуты, полям-объектам - элементы, полям-спискам(List<>) - элементы с вложенными списками. Т.е. вариант XML, в котором это дело наиболее читабельно глазами. Если не боитесь пауков - можете глянуть на этот мрак.
По мере расширения этого кодогенератора (простые сущности-записи -> сущности с полями-списками -> поддержка enum -> поддержка полей-вложенных объектов -> поддержка memo-полей (строки в которых длинный текст с переносами)) это все понемногу превратилось в жопный ад. 10 кб текста, 300 строк, в которых даже я с трудом ориентируюсь уже.
Использовать это очень просто, расширять дальше - уже сложно.
Кроме того, во всей этой кодогенерации очень бесит необходимость постоянно расширять систему типов. Т.к. один и тот же string в целевой проге - это может быть и коротенькое имя, и текст SQL запроса и RTF-документ. А при показе поля с датой пользователю может быть необходимо использовать culture-invariant формат (например я для служебных данных и для себя пользуюсь везде только им, чтобы не вспоминать "на какой локали я сижу") а может и формат из текущей локали.
В общем, если делать классическими дотнетовскими средствами, как положено, то на каждое поле или проперть в POCO-сущности навешивается еще 5-10 атрибутов, и по всему коду расползается анализ этих атрибутов, т.е. фактически мало того, что мы расширяем систему типов дополнительными описаниями, так еще и всю их обработку делаем в рунтайме, как с какой-то динамической типизацией.
PS: Во, вспомнил еще идею, которая мне насчет T4 в голову пришла.
Частенько в C# не хватает ADT, но их можно сымитировать, сделав базовый класс и унаследовав от него все варианты ADT с нужными полями. Чтобы сделать pattern matching поверх этого, нужно написать в базовом классе метод, в который передается в качестве параметров по одному делегату на каждый вариант ADT и он анализирует текущий тип объекта, вызывая соответствующий делегат, подставляя поля варианта в качестве параметров делегату.
Руками такое писать очевидно влом. Но можно сделать на T4 мелкий шаблончик, который из описания ADT в привычном виде сгенерит все классы, конструктора, прототипы делегатов и метод для pattern matching.
no subject
Date: 2010-08-12 12:09 pm (UTC)no subject
Date: 2010-08-12 12:10 pm (UTC)Глаза в общем-то на лоб лезут :)
no subject
Date: 2010-08-12 12:15 pm (UTC)но три ложки это както по-божески
я хуярил полкружки заварки типа принцесса нури или ещё какаято и полкружки воды, потом приходилось пить кальций, потому что начали разваливаться зубы, например
а потом перешёл на пуэр, он вроде полегче и както очень доставляет
только правильный пуэр в чорных блинах, а не рассыпное тащетам гуано от ооо кофеин, например
no subject
Date: 2010-08-12 12:16 pm (UTC)Хотя вообще всякие оолонги обычно пью.
Нащот кальция это чо-то да.
(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2010-08-12 12:27 pm (UTC)А если заворачивать в прокси-обьекты, которые для работы с внешними API умеют implicit operator string(RTFText text) { return text.value; }
?
no subject
Date: 2010-08-12 12:39 pm (UTC)С кодогенератором завернуть не проблема, но в итоге это выглядит как натягивание совы на глобус, то бишь обход ограничений ООП еще большим ООП.
Ну и отличий для собственно кода кодогенератора никаких не будет - что так нужно во входных данных(модели) отдельно описывать несколько случаев и отдельно обрабатывать, что так.
no subject
Date: 2010-08-12 12:27 pm (UTC)no subject
Date: 2010-08-12 12:43 pm (UTC)Тут по самой сути задачи безумие - в одной модели хранятся описания данных и описания маппингов этих данных на другие представления. Тут это "удобный" XML и юзерочитабельное представление, а у меня ж еще предстоит генерить схему БД, маппинги в нее, желательно GUI и что там еще, бинарную сериализацию какую-нибудь. Но это все я уже делаю на F#, с AST и прочими заморочками, т.к. C# из-за отсутствия ADT не хватает для некоторых сложных случаев.
no subject
Date: 2010-08-12 12:54 pm (UTC)no subject
Date: 2010-08-12 12:57 pm (UTC)no subject
Date: 2010-08-12 12:59 pm (UTC)Одно плохо - всё это счастье теперь на C# переписать, оно ж у меня на делфи. Но перепишу обязательно, универсальный же инструмент получился.
(no subject)
From:(no subject)
From:no subject
Date: 2010-08-12 01:03 pm (UTC)P.S. Упс, походу я этим комментом шаблоны порву
no subject
Date: 2010-08-12 01:06 pm (UTC)Тоже самое по сути.
Выглядит пиздато, писать заебёшься.
no subject
Date: 2010-08-12 01:12 pm (UTC)Я, правда, camlp4 не смотрел, но подумываю, что неплохо было бы T4 на F# портировать :)
no subject
Date: 2010-08-12 01:11 pm (UTC)У TH выход вроде AST как раз и тайпчекинг там на ходу как-то делается. А тут выход - текст.
Кстати, для отладки промежуточный текст очень сильно удобен, в отличие от генерации кода на ходу типа лиспов и TH.
no subject
Date: 2010-08-19 11:32 am (UTC)Как-то. Руками.
>>Кстати, для отладки промежуточный текст очень сильно удобен
-ddump-*
no subject
Date: 2010-08-12 03:12 pm (UTC)no subject
Date: 2010-08-12 03:45 pm (UTC)no subject
Date: 2010-08-12 03:52 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2010-08-12 06:15 pm (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2010-08-12 06:14 pm (UTC)no subject
Date: 2010-08-13 01:25 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2010-08-12 06:48 pm (UTC)no subject
Date: 2010-08-12 07:08 pm (UTC)no subject
Date: 2010-08-12 07:09 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: