LINUX.ORG.RU

\[C#, F#, Haskell\]Метапрограммирование в статических ЯП

 , ,


1

5

Есть вполне конкретная задача. Нужно типизировать достаточно большой объем данных. Есть описания типов и правила, по которым из этих описаний можно построить типы. Пример описания: (A nil nil (m :type Object :arg «m» :get t :set t)), это просто из головы, что значит - тип A, не имеет базового типа, не реализует интерфейсов, имеет слот m, который при создании типа должен быть взят из входных данных(имеющих заранее известный тип) по ключу «m» и для которого есть стандартные методы get и set.

Дело в том, что объект типа A создается по xml и является его типизированным описанием. К примеру, для инициализации типа A подойдет следующий xml:

<item> <m> I'mm </m> </item>

не очень важно но мало ли - есть описания обобщенных типов, и способ решения коллизий, если по одним входным типам можно построить несколько типов, к примеру если есть тип B =eq= A

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

К примеру, решение такой задачи на том же Common Lisp, выглядит вполне простым, это ничто иное как:

  • iDSL для описания типов
  • макрос, содержащий логику вывода/создания типа из iDSL

    Интересует, в первую очередь, подход для C# или F#, но интересно посмотреть на любой строго типизированный ЯП

Есть вполне конкретная задача.

Может, она и конкретная, но сформулирована очень сумбурно.

Нужно типизировать достаточно большой объем данных.

Создать в програм ме объекты, руководствуясь данными из XML-файла? А это точно метапрограммирование?

tailgunner ★★★★★
()
Ответ на: комментарий от dave

разве тип пользовательского класса не есть его класс в данном случае? Я имею в виду классы, так как строится именно реализация типа, извините за неточность

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от tailgunner

Может, она и конкретная, но сформулирована очень сумбурно.

что конкретно не понятно

Создать в програм ме объекты, руководствуясь данными из XML-файла? А это точно метапрограммирование?

ну на примере CL это реализация классов в рантайме по коду программы. Можно, к примеру, не создавать классы в рантайме, а генерировать исходник для статических ЯП, это тоже является решением

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

что конкретно не понятно

Всё.

ну на примере CL это реализация классов в рантайме по коду программы

Ты хочешь в существующую программу добавить новые классы и загрузить из XML-файла объекты этих классов?

tailgunner ★★★★★
()
Ответ на: комментарий от dave

по-моему разница очень размыта и в C#, единственное, когда я объявляю тип до его реализации в F#, я так понимаю, что это именно тип, а после реализации это уже класс, реализующий тип?

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от tailgunner

я понял, что да. правда метапрограммирование тут не нужно, если он не хочет ещё и методы в xml хранить.

aptyp ★★★★
()
Ответ на: комментарий от tailgunner

Ты хочешь в существующую программу добавить новые классы и загрузить из XML-файла объекты этих классов?

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

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от aptyp

в описании класса, на каком либо dsl, не обязательно xml, может храниться информация для реализации метода в том числе

pseudo-cat ★★★
() автор топика

Так и в чем проблема пройтись по файлу описания типов и сгенерировать классы с указанными в описании полями и методами? Или в чем вопрос?

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

staseg ★★★★★
()
Последнее исправление: staseg (всего исправлений: 2)
Ответ на: комментарий от pseudo-cat

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

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

tailgunner ★★★★★
()
Ответ на: комментарий от staseg

генерировать исходник, к примеру, C#?

сгенерировать классы с указанными в описании полями и методами

как к примеру? не писать же свой компилятор :)

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от tailgunner

разница огромна, к примеру, на CL есть средства для метапрограммирования и эта задача решается достаточно тривиально. Писать генератор кода того же C++ я бы не взялся. Другое дело, что в том же C#, вроде есть какой-то способ описывать классы на уровне платформы, типа addMethod, addSlot, etc. Но я не разбирался. Простое howto, как это сделать в вашем любимом статическом ЯП я бы очень хотел увидеть.

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

генерировать исходник, к примеру, C#?

Конечно

как к примеру? не писать же свой компилятор :)

Почему бы и нет? Если боишься этого слова, напиши транслятор своего подмножества xml (описание типов) в код на C#.

staseg ★★★★★
()
Ответ на: комментарий от pseudo-cat

естественно, CL хоть и динамический, но эта задача может решаться на нем также как и в статическом, т.е. кодогенерация за счет макросов

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

Другое дело, что в том же C#, вроде есть какой-то способ описывать классы на уровне платформы, типа addMethod, addSlot, etc.

А какая разница - вызвать addMethod или записать строчку в файл?

staseg ★★★★★
()

Ну в CL это просто. В F#... теоретически вроде должно быть можно... Там есть такая либа: http://fsharppowerpack.codeplex.com/

Там были тулзы для генерации кода в рантайме по AST. Но есть ли там объявления классов - не знаю.

Norgat ★★★★★
()
Ответ на: комментарий от staseg

CL, Haskell, C# - высокоуровневые ЯП общего назначения. Так почему для решения такой тривиальной задачи на них, кроме CL, обязательно нужно писать свой компилятор, почему в них нет готового? Даже если не брать особенности CL, а делать на нем кодогенерацию без дальнейшей загрузки в рантайм, получается, что CL сильно выигрывает для этой задачи, настолько сильно, что на других ЯП для ее решения требуется реализация синтаксического транслятора этого ЯП

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от staseg

ну к примеру не нужно описывать правила трансляции, этот этап просто опускается

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от Norgat

спасибо, посмотрю, PowerPack очень выручает на F#

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

Так почему для решения такой тривиальной задачи на них, кроме CL, обязательно нужно писать свой компилятор, почему в них нет готового?

В .NET есть CodeDom. Там можно генерировать классы, но после CL, боюсь, тебе не понравится совершенно ;)

dave ★★★★★
()
Ответ на: комментарий от pseudo-cat

CL, Haskell, C# - высокоуровневые ЯП общего назначения. Так почему для решения такой тривиальной задачи на них, кроме CL, обязательно нужно писать свой компилятор, почему в них нет готового? Даже если не брать особенности CL, а делать на нем кодогенерацию без дальнейшей загрузки в рантайм, получается, что CL сильно выигрывает для этой задачи, настолько сильно, что на других ЯП для ее решения требуется реализация синтаксического транслятора этого ЯП

В CL ты тоже будешь писать компилятор, если только исходный язык не является скобчатым DSL, т.е. в общем случае всегда.

ну к примеру не нужно описывать правила трансляции, этот этап просто опускается

Я не умею C#, но на C++ при наличии готовой xml-библиотеки твоя задача решается в несколько десятков строк, а «правила трансляции» представляют из себя одну-две строчки printf-like форматирования.

staseg ★★★★★
()
Ответ на: комментарий от pseudo-cat

Кстати, а как идея протестировать ABCL в боевых условиях? Они перевалили за психологически важный релиз 1.0.

dave ★★★★★
()
Ответ на: комментарий от pseudo-cat

CL, Haskell, C# - высокоуровневые ЯП общего назначения. Так почему для решения такой тривиальной задачи на них, кроме CL, обязательно нужно писать свой компилятор, почему в них нет готового?

Для C# возьми CodeDom, через него можно генерировать исходник на C# и компилировать в сборку.

encyrtid ★★★★★
()
Ответ на: комментарий от staseg

если только исходный язык не является скобчатым DSL

целевой язык естественно Common Lisp, если я пишу данную задачу под CL, если под С++, то С++. То, что генерация С++ в С++ задача более сложное чем генерация в CL в CL, я думаю вы спорить не будете

на C++ при наличии готовой xml-библиотеки твоя задача решается в несколько десятков строк

и все таки вам нужно описывать правила синтаксиса С++ при генерации текста, а в CL можно работать непосредственно с кодом, в макросе. к примеру:

  • int sum (int a, int b) { return a + b;} -> String.formate(«%s %s (###) {return ###}»), ...), где ### такие же правила на printf или аналоге
  • (defun sum (a b) (+ a b)) -> (defun ,arg #1=,(loop for arg in f-args collect arg) (funcall ,f-arg @#1#)), вообщем все, шаблон готов
pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

Писать генератор кода того же C++ я бы не взялся.

Я бы тоже, но это уже проблемы Си++ (или моего недостаточного знания Си++). На Python я писал кодогенераторы (для Си) в совершенно «статическом» стиле, никаких принципиальных сложностей - CST, AST, код.

в том же C#, вроде есть какой-то способ описывать классы на уровне платформы, типа addMethod, addSlot, etc

Ну, про создание классов в рантайме я уже сказал.

Простое howto, как это сделать в вашем любимом статическом ЯП я бы очень хотел увидеть.

Мой любимый статический язык - Си %)

почему для решения такой тривиальной задачи на них, кроме CL, обязательно нужно писать свой компилятор, почему в них нет готового?

Ну вот, подбираемся к сути вопроса. Какого уровня компилятор тебе нужен?

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от pseudo-cat

Только имей в виду, что в .NET есть фатальная проблема с загрузкой сборок (*.dll) - их можно загрузить в домен (application domain), и только домен можно целиком удалить. Другими словами, нет сборки мусора для классов. А так, CodeDom вполне умеет создавать сборки.

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

По крайней мере, так было до .NET v4.

dave ★★★★★
()
Ответ на: комментарий от dave

да да, я помню) еще помню, что с компиляцией F# там какие-то проблемы были, вот, кстати, код нашел в одном из своих проектов -

        let asm = System.Reflection.Assembly.Load(System.IO.File.ReadAllBytes(full_path))
        let run = asm.GetType("ATChecker.Test").GetMethod("run")
        let run () = run.Invoke(null, [||])
        ignore( run() )
        Sandbox.add_active_test()
но это уже другая история, мне не нужно сейчас делать имитацию расширения среды в рантайме

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

То, что генерация С++ в С++ задача более сложное чем генерация в CL в CL, я думаю вы спорить не будете

Все очень неоднозначно. Несомненно, генерировать сразу АСД проще, чем генерировать исходник ЯВУ, однако принципиально во втором нет ничего сложного, пара лишних строк. Кроме того многие макрописатели грешат написанием read-only макросов, даже в твоем тривиальном примере навскидку сложно понять, что делает макрос, потому что выходной шаблон перемешан с логикой генерации.

`(defun ,name ,args ,body) - понятно,
`(defun ,arg ,(loop ... ) ,@(yoba)) - не понятно.
Приводя свой макрос к нормально читаемому виду ты сраняешься в количестве строк с С++.

Это касалось простой генерации по набору однозначных правил. Если ты возьмешься генерировать что-то сложное, ты и в CL, и в C++ будешь генерировать свое АСД, которое в несколько этапов оптимизации переводить в конечный язык.

staseg ★★★★★
()
Последнее исправление: staseg (всего исправлений: 1)
Ответ на: комментарий от tailgunner

заметь, мы решаем одну и ту же задачу, только тебе нужно сделать для решения на порядок больше. Это все равно что говорить - зачем нам машина, которая сама будет считать уравнения, нам достаточно чтобы она умела складывать вычитать и умножать, а уравнения мы сами приведем к виду, где нужно только складывать, вычитать и умножать.

Макропроцессор лиспа позволяет абстрагироваться от работы с текстом и дает возможность напрямую работать с AST. А тебе нужно еще преобразовать в него. и правила дальнейшего преобразования в код написать. Что и есть уже компилятор, разве нет, вроде простых компиляторов из PAIP. Кстати, раз так, то еще нужно выбрать правила обхода AST, проверить на корректность и устранить неоднозначности.

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

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от staseg

Если ты возьмешься генерировать что-то сложное, ты и в CL, и в C++ будешь генерировать свое АСД, которое в несколько этапов оптимизации переводить в конечный язык.

насчет оптимизаций и AST согласен. не понятно, зачем генерировать из AST в конечный ЯП, если это лишний этап работы, причем если верить книжкам о компиляторах - довольно сложный, от которого можно избавиться выбрав другой ЯП?

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

Заметь, мы решаем одну и ту же задачу, только тебе нужно сделать для решения на порядок больше.

Если ты о Си - возможно, но я давно не писал трансляторов на Си.

Макропроцессор лиспа позволяет абстрагироваться от работы с текстом и дает возможность напрямую работать с AST

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

А тебе нужно еще преобразовать в него.

То, что никто не написал простой в использовании преобразователь (грамматика, строка) -> AST - плохо, но см. выше.

и правила дальнейшего преобразования в код написать.

Воот. У меня как раз на этот код и уходит подавляющая часть времени. И я не вижу, как CL мне здесь поможет.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Воот. У меня как раз на этот код и уходит подавляющая часть времени. И я не вижу, как CL мне здесь поможет.

предположим, что CL - это и есть твое AST, чуешь?:)

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

  • транслятор в AST нужно как для CL так и для С писать, ок
  • как заметил один человек, оптимизации и там и там нужно делать, ок
  • транслятор из AST в target язык для CL не нужно писать(в частном случае).

    вот и думай кому проще живется, лисперу или тебе) Кстати, какими правилами обхода AST для Python ты пользуешься?)

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

зачем генерировать из AST в конечный ЯП, если это лишний этап работы, причем если верить книжкам о компиляторах - довольно сложный, от которого можно избавиться выбрав другой ЯП?

Ну это уже зависит от задачи и от реализации. Скорее всего твой AST сам по себе не будет представлять код (ты же не будешь оптимизировать AST лисповыми кодволкерами?). Твой выходной AST (в случае, если он сразу и код) будет совершенно человеконечитаем. Твой выходной AST может оказаться нечитаемым даже лисповым считывателем.

А вообще лексический анализ входного языка и генерация выходного языка - самые простые вещи в компиляторе.

staseg ★★★★★
()
Ответ на: комментарий от pseudo-cat

можно генерировать исходники просто, мне все равно

Ну так напиши транслятор, в чём проблема?

Dragon59 ★★
()
Ответ на: комментарий от pseudo-cat

предположим, что CL - это и есть твое AST, чуешь?:)

Чую. И что? Этот AST нужно проверить и преобразовать, а для этого нужно написать код. Не вижу разницы с не-CL.

транслятор из AST в target язык для CL не нужно писать(в частном случае).

Для этого случая - вероятно, выигрыш есть.

Кстати, какими правилами обхода AST для Python ты пользуешься?)

Эээ... думаю, я не понял твой вопрос, но depth-first обычно.

tailgunner ★★★★★
()
Ответ на: комментарий от staseg

в общем, если подытожить, в случае написания качественного компилятора, нужно непосредственно написание качественного компилятора. в случае, если нужен относительно простой транслятор, парсер считается уже есть, в CL достаточно написать пару макросов для преобразования описания в AST, во многих других ЯП нужно писать компилятор. Или я вас не правильно понял?

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

Думаю, понял ты меня не правильно или не совсем правильно. В простом случае, как у тебя, на CL ты компилируешь сразу в АСД, «во многих других ЯП» ты компилируешь сразу в ЯВУ. Называть эти простые преобразования компиляцией, трансляцией или как-нибудь еще - решай сам, но фактически процессы в обоих случаях очень похожие. CL подкупает лишь в частных случаях тем, что позволяет макросами относительно просто обрабатывать скобчатые DSL.

staseg ★★★★★
()

[In case of Haskell.]

(A nil nil (m :type Object :arg «m» :get t :set t))

> runQ [d| newtype A = A { m :: String } |]
[NewtypeD [] A [] (RecC A [(m,NotStrict,ConT GHC.Base.String)]) []]

если использовать родной для TH способ описания типов чем-то не подходит, то можно мимикрировать под CL:

iDSL для описания типов

data MyTypeDSL = ...

макрос, содержащий логику вывода/создания типа из iDSL

myTypeGen :: ... -> MyTypeDSL -> Q [Dec]
myTypeGen ... = ...

и использовать этот «макрос» как

$(myTypeGen ... (typeRep ...))

оно будет само писать правильный хаскельный код во время компиляции. Для внешних парсеров и принтеров хаскельного кода (с неполной TH-интерпортабельностью) есть пакеты haskell-src[-meta, -ext[-qq]].

quasimoto ★★★★
()
Ответ на: [In case of Haskell.] от quasimoto

Кстати, для Scala тоже на эту тему что-то появилось. Но я пока не смотрел.

quasimoto ★★★★
()
Ответ на: комментарий от pseudo-cat
[| 2 + 2 |]                                                 '(+ 2 2)

[d| f x = x |]                                              '(defun f (x) x)

[d| newtype A = A { m :: String } |]                        '(defstruct a (m :type string))

data MyTypeDSL                                              эм, не очень-то принято в CL делать алгебраические
  = Type Name (Maybe BaseType) [IFace] [Slot]               типы как суммы deftype/or вокруг произведений
  | ...                                                     defstruct/defclass и использовать их для представления
                                                            AST, так что тут просто какой-то не фиксированный
                                                            подтип скобочных выражений, так что:

typeRep :: MyTypeDSL (т.е. терм типа MyTypeDSL)             (defparameter *type-rep*
typeRep = Type "A" Nothing [] [Con "m" ... ну и т.д.]         '(A nil nil (m :type Object :arg "m" :get t :set t)))

myTypeGen :: ... -> MyTypeDSL -> Q [Dec]                    (defmacro my-type-gen (... type-rep)
myTypeGen ... = ...                                           ...)

Пора хаскельному коду самому писаться для A!                Аналогично для CL

$(myTypeGen ... typeRep)                                    (my-type-gen ... *type-rep*)
quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

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

Type Name (Maybe BaseType) [IFace] [Slot]

это вообще что, объявление типа или разрабатываемый DSL?

myTypeGen :: ... -> MyTypeDSL -> Q [Dec]

::? ... ? Q[Dec]?

myTypeGen ... = ...

?

в общем спасибо за попытку, но это слижком сложно для меня

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от pseudo-cat

это вообще что, объявление типа или разрабатываемый DSL?

MyTypeDSL это тип (ADT) разрабатываемого DSL, Type - один из конструкторов этого DSL. Тип MyTypeDSL фиксирует все возможные выражения разрабатываемого DSL, а Type Name (Maybe BaseType) [IFace] [Slot] - сигнатура (часть индуктивного определения ADT) описывающая одного из возможных выражений, то есть конструктор Type :: Name -> Maybe BaseType -> [IFace] -> [Slot] -> MyTypeDSL.

Так что выражение (Type «A» Nothing [] [Con «m» ...]) будет термом типа MyTypeDSL, то есть аналогом выражения (A nil nil (m :type Object :arg «m» :get t :set t)) (тип которого никак не фиксирован - мы не можем в статике проверить что данный сексп это именно выражение нашего DSL, а не просто какой-то сексп).

В С++ MyTypeDSL бы был классом, Type - конструктором этого класса. А объекты класса полученные с помощью конструктора Type - «термами типа MyTypeDSL представляющими выражения разрабатываемого DSL».

::?

Объявление типа функции в хаскеле.

... ?

[...] у меня там с обеих сторон вместо [подставить нужное]. То есть если там больше параметров у функции myTypeGen нужно, ну и саму реализацию макроса я не выписываю.

Q[Dec]?

Это man template haskell, долго рассказывать ([] - списки, Dec - AST представление top-level деклараций, а Q - монада квазицитирования, если что :)).

?

Определение функции в хаскеле.

но это слижком сложно для меня

Сам тег поставил - я думал ты его знаешь.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Сам тег поставил - я думал ты его знаешь.

интересно посмотреть было, спасибо, мое любопытство исчерпано :)

pseudo-cat ★★★
() автор топика
Ответ на: комментарий от staseg

фактически процессы в обоих случаях очень похожие

но это тем не менее совершенно разные вещи. Я все же работаю с AST, а ты с текстом. Разве нет? А если вдруг ты заметил, что можно добавить оптимизацию, сколько времени ты потратишь на переработку простого транслятора на printf в полноценный, для работы с AST?

относительно просто обрабатывать скобчатые DSL.

это все равно, что сказать - в Java есть интсрумент, который позволяет относительно легко обрабатывать Java. Только такого инструмента нет

pseudo-cat ★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.