stdray: (Default)
[personal profile] stdray
Продолжение о синтаксических макросах немерле. Начало тут.


Макрооператоры


Я уже выше показывал, как можно с помощью макросов вводить новый синтаксис. Но существует еще один способ — макрооператоры. С их помощью можно определять новые операторы. Вариант с перегрузкой операторов как в C# никто не отменял, но в некоторых ситуациях хочется определить оператор для типов, к которым нет доступа, либо семантика оператора такова, что к какому-то конкретному типу его привязать сложно.

В качестве примера выкладываю код петуш оператора, который имитирует питоновское умножение числа на строку, то есть просто повторяет строку указанное число раз:

  1. [assembly: Nemerle.Internal.OperatorAttribute ("ArticleMacros""*>"false, 259, 259)]    
  2. macro @*>(str : PExpr, mult : PExpr) {  
  3.     StringMultImpl.DoTransform(Macros.ImplicitCTX(), str, mult)  
  4. }  
  5.   
  6. module StringMultImpl {  
  7.   
  8.     public DoTransform(typer : Typer, str : PExpr, mul : PExpr) : PExpr {  
  9.         Macros.DefineCTX(typer);  
  10.         def strType = typer.TypeExpr(str).Type;  
  11.         when(!strType.TryRequire(typer.BindType(<[string]>)))   
  12.             Message.Error(str.Location, $"Operator *> left argument requared string, got $strType");  
  13.         def mulType = typer.TypeExpr(mul).Type;  
  14.         when(!mulType.TryRequire(typer.BindType(<[int]>)))   
  15.             Message.Error(str.Location, $"Operator *> rigth argument requared int, got $mulType");  
  16.         <[string.Join("", NList.Repeat($str, $mul));]>  
  17.     }  
  18. }  


Существенным в этом примере, является атрибут уровня сборки
[assembly: Nemerle.Internal.OperatorAttribute ("ArticleMacros", "*>", false, 160, 161)]
в котором описываются параметры оператора:
- пространство имен, в котором определен оператор,
- его имя (Кстати можно использовать не только ХАСКИАРТ, но любые другие символы. С таким же успехом оператор мог называться strReply)
- является ли оператор унарным (в противном случае — бинарный. КО)
- сила связывания слева
- сила связывания справа

Сила связывания задает ассоциативность оператора. Если сила слева меньше чем справа, оператор будет левоассоциативным и наоборот. Кроме того она задает приоритет оператора. Это работает неочевидным образом, по крайней мере для меня, потому я покажу это на примере для выражения "ООО"+"ЗАО" *> 3*3+4:

  1. Силы (300, 300) → "ООО" + string.Join("", NList.Repeat("ЗАО", 3)) * 3 + 4  
  2. Силы (260, 260) → "ООО" + string.Join("", NList.Repeat("ЗАО", 3 * 3)) + 4  
  3. Силы (239, 239) → string.Join("", NList.Repeat("ООО" + "ЗАО", 3 * 3 + 4))  
  4. Силы (239, 240) → string.Join("", NList.Repeat("ООО" + "ЗАО", 3 * 3)) + 4  
  5. Cилы (242, 239) → "ООО" + string.Join("", NList.Repeat("ЗАО", 3 * 3 + 4))  


Думаю, понятно, что необходимо учитывать возможность взаимодействия по крайней мере с операторами из стандартной библиотеки. Посмотреть их приоритеты можно где-то здесь и здесь. Сам я пользуюсь grep'ом по исходникам Nemerle по фразам «OperatorAttribute» или «OperatorInfo».

При описании оператор If-Else я упомянул, что для того, чтобы парсер мог распознать вызов макроса в исходном коде, синтаксис макроса должен начинаться с константного префикса. Макрооператоры позволяют в некоторых случаях обойти это ограничение, хотя придется проделать некоторое количество ручной работы. Таким образом можно создавать тернарные операторы (а можно и n-арные).

Как-то я обнаружил, что из-за того, что F# не умеет работать с объектами типа dynamic, передача данных во View (речь про ASP.NET MVC) стала пестрить уродливым доступом к значению в словаре по его ключу. Но для F# это не было проблемой, потому что сахар валялся на поверхности. Когда я стал разбираться с Nemerle мне захотелось повторить данный прием. Вышло как-то так:

  1. [assembly: Nemerle.Internal.OperatorAttribute ("ArticleMacros""?"false, 142, 139)]  
  2. macro @?(dynObj, expr) {  
  3.     DynamicAssignmenImpl.DoTransform(Macros.ImplicitCTX(), dynObj, expr)  
  4. }  
  5.   
  6. module DynamicAssignmenImpl{  
  7.     public DoTransform(typer : Typer, dynObj: PExpr, expr: PExpr) : PExpr {  
  8.         Macros.DefineCTX(typer);  
  9.         def dynObjType = typer.TypeExpr(dynObj);  
  10.         when(! dynObjType.Type.TryRequire(typer.BindType(<[ IDictionary[stringobject] ]>))) {  
  11.             def msg = $"Required IDictionary[string, object], but got $(dynObjType.Type)";  
  12.             Message.Error(dynObj.Location, msg);  
  13.         }  
  14.         match(expr) {  
  15.             | <[$(prop: name) = $e]> =>   
  16.                     def key = prop.ToString();  
  17.                     <[ if($dynObj.ContainsKey($key))   
  18.                         $dynObj[$key] = $e;  
  19.                     else   
  20.                         $dynObj.Add($key, $e); ]>  
  21.             | <[$(prop: name)]>      =>   
  22.                     <[ $dynObj[$(prop.ToString())] ]>;  
  23.             | <[$e]>                 =>   
  24.                     def msg = $"Expected syntx dyn?PropertyName or dyn?PropertyName = value, got $e";  
  25.                     Message.Error(expr.Location, msg);  
  26.                     <[()]>     
  27.         }  
  28.     }  
  29. }  


Мой оператор умеет не только добавлять пару ключ-значение в словарь, но и обновлять значение, если ключ уже существует, а также возвращать значение по ключу, если пользователь ничего присваивать не стал. При этом работа с таким оператором, мало отличается от работы с родным dynamic из C#

  1. //Небольшое, но гордое русскоязычное комьюнити немерлистов  
  2.        //испытывает сильнейшую боль от ентой точки  
  3.        def dict = Dictionary.[stringobject]();  
  4.        dict?LOL = 9+8;  
  5.        dict?YOBA = DateTime.Now;  
  6.        WriteLine($"LOL = $(dict?LOL); YOBA = $(dict?YOBA)");  
  7.        dict?YOBA = "Another YOBA";  
  8.        WriteLine($"LOL = $(dict?LOL); YOBA = $(dict?YOBA)");  


Самое интересное в этом примере — это сопоставление квази-цитаты с образцом, которое позволяет достаточно удобным способом выделять из квази-цитаты составные части. Возможность формировать цитату из фрагментов внешнего AST или объектов иного типа, а также разбирать квази-цитату на составляющие, называется сплайсингом. Подробнее о сплайсах в Nemerle можно почитать здесь.


Что-то вроде заключения первой части


Хотя мой макрооператор @? может показаться бесполезным, он демонстрирует, как макросы могут изменять семантику языка, вводя в нее новый конструкции. Они позволяют на базе простого класса вроде public class YobaDynamic : Dictionary[string, object] { } реализовать половину петуш Питона, при желании.

Я постарался максимально компактным образом описать то, что узнал про синтаксические макросы Nemerle. Как можно видеть, писать их относительно несложно. Сложнее продумать логику работы макроса и его взаимодействие с внешним миром (например макросы Nemerle не поддерживают перегрузку, и можно нечаянно что-то сломать, особенно это просто сделать макрооператорами). Макросы могут быть вредны, поскольку то, что кажется красивым, логичным и читаемым для одного человека, может выглядеть как сраный ХАСКИАРТ для другого. В то же время, такие вещи как синтаксис LINQ в C# или do-нотация в Haskell можназделость с помощью этих самых макросов. Это позволяет поднять читаемость и разделить предметную область и реализацию. Это имеет непосредственное отношение к созданию DSL, то есть решение задачи в терминах предметной области без привлечения неведомой ёбы вроде СЕРИАЛИЗАТОРОВ или там РЕГИСТРОВ ПРОЦЕССОРА, как в каменном веке. В тоже время придумать полноценный DSL (SQL, HTML и так далее), который на 100% покроет все возможные юзкейсы — задача практически невыполнимая. И разумным выходом выглядит создание eDSL (e – embeded, то есть встроенных в язык). Эту идею я вычитал вот в этом посте. В данном контексте Nemerle представляется достаточно удобным инструментом, поскольку позволяет на ура лепить эти самые eDSL'и.

Синтаксическим макросами возможности макросистемы Nemerle не ограничиваются. Существуют также макроатрибуты применимые к следующим объектам: Class, Method, Field, Property, Event, Parameter, Assembly. Для меня сейчас они представляют наибольший интерес, поскольку именно через них осуществляется столь необходимая мне кодогенерация. Но об этом как-нибудь в другой раз.

(no subject)

Date: 2012-07-16 09:29 am (UTC)
From: [identity profile] thesz.livejournal.com
>В данном контексте Nemerle представляется достаточно удобным инструментом, поскольку позволяет на ура лепить эти самые eDSL'и.

Лепить-то ладно, все могут, вот совмещать!..

(no subject)

Date: 2012-07-16 09:37 am (UTC)
From: [identity profile] stdray.livejournal.com
Существенными являются, на мой взгляд, следующие вещи:
1) eDSL'и лежат в отдельных библиотеках и открываются по требованию
2) все работает в рамках одной системы типов Nemerle, потому имеется возможность создавать интерфесы для передачи данных между ентими eDSL'ями.

На данный момент, плохо, что нет перегрузки макросов, как и нет сообщений от компелятора о перекрытии одного макроса другим, особенно заметно это для макрооператоров.

(no subject)

Date: 2012-07-16 09:45 am (UTC)
From: [identity profile] thesz.livejournal.com
Существенной вещью является возможность произвольной комбинации и параметризации EDSL и работающего с ними кода.

Простой пример. Можно ли передать макрос параметром? Может ли какой-то код быть параметризован библиотекой с макросами?

(no subject)

Date: 2012-07-16 10:04 am (UTC)
From: [identity profile] stdray.livejournal.com
>Существенной вещью является возможность произвольной комбинации и параметризации EDSL и работающего с ними кода.

Я не очень понимаю, что имеется в виду. Допустим, у меня есть есть edsl для описания роутинга, есть edsl для описания шаблонов генерации html. Что в данном случае следует считать их комбинацией? Из edsl описания шаблонов можно получать информацию о маршрутах - это пример интерфейса между edsl, как его представляю себе я. А на работе у меня формируются таможенные декларации и, если я сделаю для него edsl, даже представить себе невозможно, как и зачем его можно комбинировать с edsl'ями, про которые я писал выше. То есть не существует никакой возможности произвольно комбинировать edsl, даже безотносительно языка.

>Можно ли передать макрос параметром? Может ли какой-то код быть параметризован библиотекой с макросами?

Нет. Это макрос что-то знает о коде, а не код о макросе. Однако макрос можно параметризовывать реализацией. Например https://github.com/rsdn/nemerle/wiki/Computation-Expression-macro В данном случае, макрос comp параметрузуется бидлером, после чего сomputation expressions разворячиваются в конструкции целевого билдера. Как-то так отделяется логика от реализации.

>Может ли какой-то код быть параметризован библиотекой с макросами?

Как это "параметризован библиотекой"?

(no subject)

Date: 2012-07-16 10:26 am (UTC)
From: [identity profile] thesz.livejournal.com
>То есть не существует никакой возможности произвольно комбинировать edsl, даже безотносительно языка.

EDSL - это скрытая библиотека (и наоборот). Как это нет возможности комбинировать библиотеки?

>>Можно ли передать макрос параметром? Может ли какой-то код быть параметризован библиотекой с макросами?
>Нет.

Вот.

Мы только что выяснили слабое место макросов. Макросы не являются первоклассной сущностью.

Чем больше в ЯП первоклассных сущностей, тем лучше. Лисп чем хорош? Практически всё является первоклассной сущностью, sexp. Макросы уже потом этим пользуются. Хаскель чем хорош? Практически всё является первоклассной сущностью, функцией. Нужда в макросах возникает сильно потом.

>Как это "параметризован библиотекой"?

В ML это называется функторы или модули, как-то так. В Хаскеле - конструкторы типов.

(no subject)

Date: 2012-07-16 11:32 am (UTC)
From: [identity profile] stdray.livejournal.com
>EDSL - это скрытая библиотека (и наоборот). Как это нет возможности комбинировать библиотеки?

EDSL - это язык, оперирующий терминами предментной области. При помощий макросов их действительно возможно создать. А комбинировать библиотеки можно сколько угодно, только не стоит называть какой-нибуть sqldatareader "EDSL'ем для работы с реляционными базами".

>Практически всё является первоклассной сущностью, sexp. Макросы уже потом этим пользуются.

Абсолюдня индентичная ситуация в Nemerle. Код в виде квази-цитат является первоклассной сущностью, которой можно оперировать при помощи макросов. Я подозреваю, что отличие только в том, что лисповые макросы можно исполнять в рантайме. В комьюнити немерле возникала идея прикрутить возможность использовать макросы для динамическом компиляции сборок, но дальше идеи дело не пошло, видимо, было не очень нужно.

>Хаскель чем хорош? Практически всё является первоклассной сущностью, функцией.

do-нотация не является первоклассной сущностью и ее нелья никуда передать. И опять же, я не могу представить себе семантику такой "передачи".

>>Как это "параметризован библиотекой"?
>В ML это называется функторы или модули, как-то так. В Хаскеле - конструкторы типов.

Это имеет отношение к системе типов и полиморфизму. В Nemerle под словом "макрос" стоит понимать расширение компилятора, которое, во-первых, добавляет новые правила в парсер, а, во-вторых, имеет доступ практически ко всем структурам, которыми оперирует компилятор (исходный код, AST, типизированное AST) и может ими оперировать для введения новых конструкций в язык. Система типов и макросистема - непротиворечивые вещи, которые можно использовать одновременно. Типы - для валидации кода из коробки и оптимизаций компилятором, макросы - для создания EDSL и кодогенерации.
Edited Date: 2012-07-16 11:39 am (UTC)

(no subject)

Date: 2012-07-16 02:29 pm (UTC)
From: [identity profile] thesz.livejournal.com
Я скажу нехитрую вещь, которую уже неоднократно повторял: синтаксические расширения типа макросов и EDSL служат для той же цели, что и типы - для раннего обнаружения ошибок. "Наглядность" - это быстрое понимание структуры кода и обнаружение неисправностей.

Только типы 1) могут быть сущностями первого класса и 2) много мощнее макросов.

>EDSL - это язык, оперирующий терминами предментной области. При помощий макросов их действительно возможно создать.

Предметные области могут быть параметризованы. Предметная область "язык программирования" может быть параметризована предметной областью компилятор и предметной областью интерпретатор. И обработка данных в этих двух комбинациях будет разной - полный сбор и преобразование в компиляторе и частичный сбор и преобразование в интерпретаторе.

Валяйте, сделайте такое на макросах.

>>Хаскель чем хорош? Практически всё является первоклассной сущностью, функцией.
>do-нотация не является первоклассной сущностью и ее нелья никуда передать. И опять же, я не могу представить себе семантику такой "передачи".

До изобретения do-нотации использовали >>=, >> и return. И сейчас используют. И теряют совсем немного (и часто выигрывают).

>Система типов и макросистема - непротиворечивые вещи, которые можно использовать одновременно.

А можно использовать только систему типов. См. зависимые типы данных.

(no subject)

Date: 2012-07-16 03:22 pm (UTC)
From: [identity profile] stdray.livejournal.com
>синтаксические расширения типа макросов и EDSL служат для той же цели, что и типы - для раннего обнаружения ошибок. "Наглядность" - это быстрое понимание структуры кода и обнаружение неисправностей.

Кроме того макросы позволяют снизить цикломатическую сложность кода, путем заталкивая бойлерплейта, неизбежно возницающего из-за несоответствия между сущностями предметной области и сущностями языка программирования обзщего назначения, в реализацию макроса.

>Только типы 1) могут быть сущностями первого класса

В случае с макросистемой: код - сущность первого класса, а макросы - нет
В случае с системой типов: тип - сущность первого класса, а сама система типов - нет.
Или существуют способы передавать систему и параметризовывать ею код?

На мой взгляд, ситуация идентичная, хотя я встречал утверждения, что макросы могут задавать систему типов, но я не понимаю, как это может работает.

>2) много мощнее макросов.

Я уже писал, что считаю типы ортоганальными макросам, потому не ясно на основании каких каких критериев можно сделать подобный вывод.

>До изобретения do-нотации использовали >>=, >> и return. И сейчас используют. И теряют совсем немного (и часто выигрывают).

do-нотация - это лишь пример синтаксического сахара, которого в Haskell в большом достатке. Или list comprehensions тоже не используются? При этом в Haskell они есть, но через типы их сделать нельзя.

>Предметные области могут быть параметризованы. Предметная область "язык программирования" может быть параметризована предметной областью компилятор и предметной областью интерпретатор.

Это не параметризация предментной области, а просто различные реализации. Это было бы очень неприятно, если бы в случае с компилятором у нас был бы один язык, а с интерпретатором - другой. Не смотря на то, что в Haskell сделано именно так: ghci забрасывать пользователя прямиков внутрь IO-монады, после чего пользователь наслаждается всеми прелестями do-нотации, полезность которой вы отрицаете, я считаю это плохой практикой. При этом проблемы с реализаций различного поведения на макросах быть не должно. В одном случае сайд-эффекты будут применятся по ходу процесса разбора и типизации, а в другом - после.

>А можно использовать только систему типов. См. зависимые типы данных.

Simon Peyton-Jones и компания решили, что для монад и списков нужен сахар, как и во многих других местах. Если бы системы типов было бы достаточно, сахар бы не множился. Сама реальность языка Haskell опровергает это вашей утверждение. В этом констексте, интересно было бы узнать, что именно натолкнуло разработчиков Скалы на запиливание макросов в язык.

(no subject)

Date: 2012-07-16 05:44 pm (UTC)
From: [identity profile] thesz.livejournal.com
>Сама реальность языка Haskell опровергает это вашей утверждение.

Я не про Хаскель говорил.

(no subject)

Date: 2012-07-16 05:51 pm (UTC)
From: [identity profile] Влад Чистяков (from livejournal.com)
> Простой пример. Можно ли передать макрос параметром? Может ли какой-то код быть параметризован библиотекой с макросами?

Эти понятия не применимы к макросам. Ты просо рассуждаешь о том что не понимаешь.

Макросы это языковые расширения и/или метапрограммы (в зависимости от типа и решаемой задачи).

Их не надо куда-то предавать. Их параметры это произвольное AST (код без интепретации). Так что в них можно передать что угодно но в виде кода. Их передавать никуда нельзя просто потому что это бессмысленно. Код же макросов - это произвольный код на Немерле. Его можно передавать куда угодно как и любую функцию и в нем можно использовать что угодно.

(no subject)

Date: 2012-07-16 06:30 pm (UTC)
From: [identity profile] thesz.livejournal.com
>Ты просо рассуждаешь о том что не понимаешь.

Vlad, иди ты нах-й!

(no subject)

Date: 2012-07-16 07:18 pm (UTC)
From: [identity profile] Влад Чистяков (from livejournal.com)
Какой феерический батхерт!

Ты бы не лез рассуждать о том в чем некомпетентен. Тогда и в подобные ситуации не попадал бы. ;)

А если уж лезешь, то для начала изучи вопрос обсуждения. Противно ведь слушать подобную чушь.

(no subject)

Date: 2012-07-16 08:08 pm (UTC)
From: [identity profile] thesz.livejournal.com
Влад, иди ты нах-й!

(no subject)

Date: 2012-07-16 09:41 pm (UTC)
From: [identity profile] Влад Чистяков (from livejournal.com)
Давай, еще! Покажи свою истинную суть. :)

(no subject)

Date: 2012-07-16 11:04 pm (UTC)
From: [identity profile] thesz.livejournal.com
Вот, перестал говорить кто в чем не разбирается, на человека стал похож.

Запомни этот опыт. Он тебе не раз ещё пригодится.

(no subject)

Date: 2012-07-16 05:58 pm (UTC)
From: [identity profile] Влад Чистяков (from livejournal.com)
> Вот.

> Мы только что выяснили слабое место макросов. Макросы не являются первоклассной сущностью.

Мы только что выяснили отсутствие у тебя компетенции по обсуждаемому вопросу. Не более того.

Макросы - это точка входа в метамир (в компилятор). А внутри это просто код к который можно обернуть функцией (лябдой, например) и передавать куда угодно. Нужно только понимать, что это метакода (работающий на стадии компиляции), так что передать его в какие-то части компилируемой программы физически невозможно.

(no subject)

Date: 2012-07-16 06:01 pm (UTC)
From: [identity profile] Влад Чистяков (from livejournal.com)
> Чем больше в ЯП первоклассных сущностей, тем лучше. Лисп чем хорош? Практически всё является первоклассной сущностью, sexp.

В Лиспе маросы очень похожие. Они так же работают во время "компиляции" программы. Только динамическая природа Лиспа позволяет интерпретировать код в ратайме. В остальном отличий нет. Макрос точно так же не первоклассная сущность в твоем понимании. Это метапрограмма занимающаяся переписыванием одного AST в другое.

December 2019

S M T W T F S
1234567
891011121314
15161718192021
222324252627 28
293031    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags