LINUX.ORG.RU

[ФП][срач] Язык программирования Pure


2

4

Pure - динамически типизированный функциональный язык программирования с моделью исполнения, основанной на идее «term rewriting».

  • Синтаксис, бликий к Haskell (включая карринг, срезы, лямбды, сопоставление с образцом, list comprehensions и «as» patterns);
  • Поддерживает модель ленивости, основанную на продолжениях и заимствованную из Alice ML;
  • Допускает функции с побочными эффектами, опциональную аннотацию типов, интерфейсные типы
  • Поддержка рациональных, комплексных чисел, встроенный RegEx;
  • Содержит спектр встроенных структур данных, таких как: списки, туплы, векторы, матрицы и ленивые последовательности;
  • Интеграция с GSL;
  • Поддерживает полноценную систему гигиенических макросов;
  • Лёгкий FFI с сишными библиотеками;
  • Реализация под LLVM под GNU/Linux, Mac OS X, FreeBSD и Windows;
  • Из коробки поддержка компиляции в нативный код;
  • В комплекте идёт набор батареек для разнообразных нужд.

Про производительность заявляется следующее:

The Pure interpreter is able to achieve very good performance, offering execution speeds in the same ballpark as good Lisp interpreters. It seems to be one of the fastest implementations of term rewriting as a programming language right now, and is certainly good enough for most programming tasks except maybe the most demanding number crunching applications (and even these can be tackled by interfacing to Fortran or C).

Язык активно развивается, последняя версия документации датирована 8 января, автор постоянно пилит своё детище, активно идёт на контакт и охотно принимает и рассматривает любые предложения.

Ссылка на страницу проекта: http://code.google.com/p/pure-lang/
Ссылка на документацию (pdf): http://docs.pure-lang.googlecode.com/hg/puredoc.pdf
Ссылка на пример полной реализации АВЛ-деревьев: http://pure-lang.googlecode.com/hg/pure/examples/avltree.pure
Репозиторий (лицензия LGPLv3): hg clone https://code.google.com/p/pure-lang/

★★★★★
Ответ на: комментарий от anonymous

Ну так и в лиспе макросов нет. Там есть обычные функции

Ну да. Только они там отделены от функций (у них свой способ определения и область имён), в хаскеле мета-система берёт обычные функции. Они не путаются с функциями-не-макросами потому что у них тип оканчивается на Q Exp и QuasiQuoter. Но при этом, обратно, способ их вызова отличается от способа вызова функций.

Т.е. в лиспе - пишем специальным образом (defmacro, etc.), вызываем обычно (голова списка), в TH - пишем обычную функцию, вызываем специальным образом (квази-цитирование [||], сплайс $()).

Речь о том, что было бы неплохо иметь и высокоуровневые средства.

Неплохо, но только в homoiconic world (хотя, я не знаю, точно ли это).

Да одинаково все на уровне использования.

Я имею ввиду, что в лиспе можно написать (if (op ...) (+ (op ...) (op ...))), где op - наш макрос, его применение не отличается от применения if или +. А в хаскеле уровень использования другой - так написать нельзя (негомоиконность, т.е. нужно специально сплайсить).

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

Ну так в этом вся соль. Вот есть у нас инструмент - макросы, если этот инструмент прост и удобен в использовании (в гомоиконном ЯП) - то «по делу» - это чуть менее, чем везде. То есть инструмент юзабелен. Если же он кривой и неудобный (как в негомоиконном ЯП), то «по делу» - это чуть более, чем нигде. То есть инструмент неюзабелен. И в этом случае нам следует обосновывать каждое его применение.

Ты ставишь знаки равенства - гомоиконность = s-выражения, удобные мета-вычисления = гомоиконность, хороший ЯП = удобные мета-вычисления (как необходимое условие). Так? Отсюда, транзитивно, выводится - хороший ЯП = ЯП основан на s-выражениях (= лисп)).

Но как быть тогда с конкретным синтаксисом? Что если я хочу инфикс/постфикс/микфикс, удобные конструкции верхнего уровня, и вообще бесскобочные специальные формы?

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

Вообще, выражение 1 op 2 op 3 даже в математике не имеет произвольного смысла

А это выражение, в свою очередь, само может быть вызовом макро-формы, соответственно и семантика у него совершенно иная.

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

Т.е. в лиспе - пишем специальным образом (defmacro, etc.), вызываем обычно (голова списка), в TH - пишем обычную функцию, вызываем специальным образом (квази-цитирование [||], сплайс $()).

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

Неплохо, но только в homoiconic world (хотя, я не знаю, точно ли это).

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

Я имею ввиду, что в лиспе можно написать (if (op ...) (+ (op ...) (op ...))), где op - наш макрос, его применение не отличается от применения if или +. А в хаскеле уровень использования другой - так написать нельзя (негомоиконность, т.е. нужно специально сплайсить).

Это же несущественно и ни на что не влияет.

Хорошо это или плохо?

Это никак :)

В лиспе вообще макросы никак не выделяются по одной простой причине - большая часть форм в коде это именно макросы и есть, то есть в данном случае было бы логичным выделять обычную аппликацию. Что как-то глупо, согласись.

Например в SBCL - eDSL на eDSL-е и прежде чем что-то делать мне нужно основательно изучить как работают его многочисленные макросы

Ну изучение работы макроса ничем не отличается от изучения работы функции. В случае, если это макрос «обычной», скажем так, сложности, конечно. Если же это какой-то ДСЛ уровня loop, то сложность аналогична сложности изучения некоторой библиотеки (что вполне логично, ведь там соответствующий функционал).

(и играющие роль такого время-вампира).

В каком смысле?

Ты ставишь знаки равенства - гомоиконность = s-выражения, удобные мета-вычисления = гомоиконность, хороший ЯП = удобные мета-вычисления (как необходимое условие). Так? Отсюда, транзитивно, выводится - хороший ЯП = ЯП основан на s-выражениях (= лисп)).

Да нет, о хороших ЯП речи вообще не шло, как и о s-выражениях, важна гомоиконность, а не скобки. Ну и между гомоиконностью и удобными метавычислениями равенство, да. В том смысле, что удобные мета-вычисления требуют гомоиконности.

Но как быть тогда с конкретным синтаксисом? Что если я хочу инфикс/постфикс/микфикс, удобные конструкции верхнего уровня, и вообще бесскобочные специальные формы?

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

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

В каком смысле?

В том смысле, что нужно перелопатить макросы используемые в коде. Повышается количество связей. Как-то другие языки обходятся исключительно теми языковыми средствами которые заложены создателями языка (функции (ФВП, при наличии), ADT, паттерн-матчинг, классы типов, классы ООП-ия). Ясно, конечно, что многие такие выразительные средства могут быть добавлены в гомоиконном языке с помощью макросов, но есть подозрения, что по достижении некой планки выразительности (наличия выразительных средств в языке) больше ничего добавлять не придётся. Например, если в каких-нибудь си или питоне все просто - немного стилевых соглашений и у всех будет одинаковый идеоматичный код, то в лиспе с макросами возможны «явления» (dwim.hu, LoL, SBCL-ные внутренние макросы).

Например, если я вдруг открою какой-нибудь файл в GHC, то я, более менее, смогу понять логику какой-либо функции. Да, при этом она будет использовать другие подсистемы, которые тоже, возможно, придётся «перелопатить». Но, открыв подобный файл в SBCL я увижу код который зависит от других подсистем не только функционально, но и на уровне макросов. Типа define ir2 translators, define instructions или define vops - там просто чудо а не макросы, своя маленькая вселенная, в остальном мире, вроде LLVM на б-г мерзком C++, в подобных местах обходятся стандартными языковыми средствами с одной стороны, и концептами с другой. В LLVM ещё, например, реализуются настоящий DSL для описания архитектур (TD), который, в принципе, можно и унаследовать, а в подобном месте у SBCL располагается eDSL - какие-то макросы «ни для кого» (т.е. исключительно его кишки, с вкраплениями обычного CL кода и завязанные на другие части).

Т.е. ещё вопрос что лучше - макросы + предметная область в eDSL, или стандартные языковые средства + внеязыковые концепты разработки + при сильной необходимости, настоящий внешний DSL. Ну вот как в этом примере с SBCL и LLVM.

важна гомоиконность, а не скобки

А есть какие-нибудь примеры гомоиконных языков не являющихся лиспами (не использующих s-выражения)?

В том смысле, что удобные мета-вычисления требуют гомоиконности.

Как нужно думать, чтобы осознать этот факт (тем кто ещё не осознал)?

Что подразумевается под «удобными конструкциями верхнего уровня»?

Типа module, import, data из хаскеля.

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

Как нужно думать, чтобы осознать этот факт (тем кто ещё не осознал)?

Кстати, реальные примеры макросов (полезных и требующих именно гомоиконности в применении) могли бы разбавить дискуссию :)

Потому что тогда можно было бы посмотреть - вот такая-то важная задача так-то решается с помощью макросов (с применением макросов, помимо других средств - понятно, что задачи не макросы решают). И посмотреть - решается ли она средствами TH (требует ли вообще привлечения TH).

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

Да, там опечатка. Надо так:

(define-syntax-rule (bad-let ((id expr) ...) body ...)
  (let ((id expr) ...) body ...))

Как бы в данном случае bad-let = let. Т.е. применение

(bad-let ((a 1) (b 2)) body)
A анонимус хотел увидеть код для случая:
(bad-let (a 1 b 2) body)
сабж :)

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

Потому что тогда можно было бы посмотреть - вот такая-то важная задача так-то решается с помощью макросов

Когда следует пользоваться макросами: PLAI, стр.335, п.36.6

Там же, в п.36.5 пишут, когда не стоит.

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

Когда следует пользоваться макросами: PLAI, стр.335, п.36.6

Не понравилось.

providing cosmetics

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

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

introducing binding constructs

Вроде let, let* - не нужно. Вроде with-* макросов - можно использовать примитивы catch/throw вместе с комбинаторами с нестрогой стратегией вычисления (типа bracket в хаскеле).

altering the order of evaluation

Аналогично - нестрогий порядок вычисления и способ вводить строгость (или наоборот, в общем случае - контроль за стратегией вычисления).

defining data languages

Для этого есть ADT.

Короче, это всё из разряда «пушкой по воробьям».

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

В том смысле, что нужно перелопатить макросы используемые в коде.

Какая разница макросы это или обычные функции?

Повышается количество связей.

За счет чего?

Как-то другие языки обходятся исключительно теми языковыми средствами которые заложены создателями языка (функции (ФВП, при наличии), ADT, паттерн-матчинг, классы типов, классы ООП-ия).

Ну да. И макросы - это как раз одно из средств, заложенных создателями языка. Наравне с функциями, ADT, паттерн-матчингом и т.д.

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

Это звучит как «есть какая-то планка, после которой новые библиотеки/функции писать не придется, потому что все и так уже написано». Ясно же, что подобные утверждения - полная чушь.

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

Так это же замечательно - вместо идеоматического лапшеобразного кода мы получаем у нас простой и понятный ДСЛ, что резко сокращает затраты на понимание кода.

Например, если я вдруг открою какой-нибудь файл в GHC, то я, более менее, смогу понять логику какой-либо функции.

Ну совершенно аналогично и для SBCL.

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

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

Т.е. ещё вопрос что лучше - макросы + предметная область в eDSL, или стандартные языковые средства + внеязыковые концепты разработки + при сильной необходимости, настоящий внешний DSL. Ну вот как в этом примере с SBCL и LLVM.

Это не вопрос. Макросы + еДСЛ _всегда_ лучше.

А есть какие-нибудь примеры гомоиконных языков не являющихся лиспами (не использующих s-выражения)?

Я не могу утверждать, что нету.

Как нужно думать, чтобы осознать этот факт (тем кто ещё не осознал)?

Надо думать следующим образом: писать (op args ...) - это удобно, писать кривой лапшекод на несколько строк, который сразу и непонятно что делает - это не удобно.

Типа module, import, data из хаскеля.

И как они связаны с синтаксисом и гомоиконностью? Никак.

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

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

Почему же требующего? Ничуть не требующего. но если можно косметику использовать и это дает огромный эффект с минимальными затратами - то почему нет? Зачем отказываться от удобного инструмента?

Для выделения кусков кода нужно использовать функциональную абстракцию, если в языке есть ФВП - тем более (комбинаторы).

Но зачем? Ведь макросы почти всегда удобнее.

Вроде let, let* - не нужно. Вроде with-* макросов - можно использовать примитивы catch/throw вместе с комбинаторами с нестрогой стратегией вычисления (типа bracket в хаскеле).

Ты не понял этого пункта.

Аналогично - нестрогий порядок вычисления и способ вводить строгость (или наоборот, в общем случае - контроль за стратегией вычисления).

И этого не понял.

Для этого есть ADT.

ADT эту задачу не решают и близко.

Вообще же вопрос «зачем нужны макросы» совершенно идиотский. Он сродни вопросу «зачем нужны функции». Ну или «зачем нужны ветвления». Просто удобный для решения многих задач инструмент.

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

Потому что тогда можно было бы посмотреть - вот такая-то важная задача так-то решается с помощью макросов (с применением макросов, помимо других средств - понятно, что задачи не макросы решают). И посмотреть - решается ли она средствами TH (требует ли вообще привлечения TH).

Ну вот опять. Речь идет не о том, что что-то можно в теории - а о практической возможности. В теории брейнфак эквивалентен по выразительности ну тому же хаскелю, значит ли это, что любую программу на хаскеле можно переписать на брейнфаке? Теоретически - да. Практически - нет, нельзя.

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

И макросы - это как раз одно из средств, заложенных создателями языка.

Не, макросы всё меняют. man отсутствие синтаксиса и семантики у CL по lovesan-у :)

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

Не, макросы всё меняют.

Это твое личное мнение, которое ничем не обоснованно.

man отсутствие синтаксиса и семантики у CL по lovesan-у :)

Ссылаться на мнение шизофреников - это сейчас считается аргументом в дискуссии?

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

Но утверждаешь, что таки лисп по лесенке выше.

Причем тут лисп? Речь идет о макросистеме ЯП с не/гомоиконным синтаксисом. Пример того, как негомоиконность порождает ужасающий бойлерплейт, уже был дан, чего еще надо?

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

Это твое личное мнение, которое ничем не обоснованно.

Один твой пост выше тоже выглядит вот так - «цитата» - «всё не так, делай как в лиспе»; «цитата» - «всё не так, делай как в лиспе»; и т.д.

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

это сейчас считается аргументом в дискуссии?

Просто это немножко правда.

Причем тут лисп? Речь идет о макросистеме ЯП с не/гомоиконным синтаксисом.

А есть какие-нибудь примеры гомоиконных языков не являющихся лиспами (не использующих s-выражения)?

Я не могу утверждать, что нету.

Пока про лисп и про TH (за неимением других претендентов на обсуждение).

Пример того, как негомоиконность порождает ужасающий бойлерплейт, уже был дан, чего еще надо?

Ничего не было дано. Я попросил переключиться с абстрактных коней (надо же - макрос let можно переписать) на что-то более реальное.

Например, я делал так - писал макрос вида define-такая-то-ерунда-предметной-области, который использовался как (define-такая-то-ерунда-предметной-области ... тут (e)DSL спецификации ...). Такие формы могут быть помещены во внешние файлы, по сути, как файлы конфигурации в s-выражениях, их в любой момент можно подгрузить в мета-циклический рантайм. При этом, во что они разворачиваются - в класс и его объект (отражающие описанную конфигурацию) и в методы для классов (отражающие способ использования объектов работающей системой). При этом, так как диспетчеризация динамическая (я про CLOS), сразу после load такого файла крутящаяся в цикле система уже знала что ей делать дальше (при вызове общих для всех классов методов на вновь попавших в пул объектах).

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

Но при этом, всё это не единственно верный (и даже не обязательно самый удобный) подход. Вместо этого можно сознательно отказаться от интерактивной инкрементальной разработки и выбрать подход, когда компилируется только целостное приложение. И для решения подобной задачи писать парсер внешнего DSL, строить AST в виде ADT и дальше обрабатывать его с помощью строго типизированных функций - принимать какие-то решения по дальнейшей работе системы. Это просто, если в языке задачи написания парсеров и работа с ADT является тривиальными.

Т.е., о чём это я, о том что, наверно, не нужно проповедовать. Лучше объяснять - формулировка

писать (op args ...) - это удобно, писать кривой лапшекод на несколько строк, который сразу и непонятно что делает - это не удобно.

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

Насчёт бойлерплейта - его лучше найти где-нибудь в пакетах string-quote, interpol, derive, derive-*, hamlet, yesod и т.п. Т.е. там где TH используется по назначению, а не в определениях специальных форм описанных в PLAI (это просто смешно).

Ну и любая другая реальная задача - welcome.

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

Один твой пост выше тоже выглядит вот так - «цитата» - «всё не так, делай как в лиспе»; «цитата» - «всё не так, делай как в лиспе»; и т.д.

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

Ничего не было дано. Я попросил переключиться с абстрактных коней (надо же - макрос let можно переписать) на что-то более реальное.

Абстрактные кони как раз у тебя, а я с самого начала дал реальный пример (op args ...).

Но при этом, всё это не единственно верный

А кто говорил, что он единственно верный?

И для решения подобной задачи писать парсер внешнего DSL, строить AST в виде ADT и дальше обрабатывать его с помощью строго типизированных функций - принимать какие-то решения по дальнейшей работе системы.

Ну да, можно использовать неудобные низкоуровневые средства вместо высокоуровневых. Что дальше?

Ну и любая другая реальная задача - welcome.

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

Ничего не проясняет - почему хорошие мета-вычисления это именно гомоиконность (читай, лисп).

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

Т.е. там где TH используется по назначению

Вот в том и дело, что «по назначению» в ЯП с гомоиконным синтаксисом - это на порядок более широкое множество задач, чем «по назначению» в ЯП с негомоиконным синтаксисом. Уже хрен знает сколько пытаюсь донести до тебя эту мысль, но никак не доходит.

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

А кто говорил, что он единственно верный?

В конце вот этого твоего поста опять есть намёк на то что он, по крайней мере, «лучше», этот подход.

Я тебе дал реальную задачу - собрать простейшее выражение

Это не реальная задача - как будто ежедневная практика состоит из собирания подобных простых выражений. Реальная задача это что-то народно-хозяйственное.

у тебя из-за отсутствия гомоиконности это вылилось в ручную сборку АСТ с кучей кода

Пошёл посмотрел - 40 знаков.

Ну да, можно использовать неудобные низкоуровневые средства вместо высокоуровневых.

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

В лиспе мы просто берем и пишем макросы на каждый чих.

При этом, есть пишущие лисперы, которые не разделяют такую точку зрения (против «писать макросы на каждый чих»).

В ЯП с гомоиконным синтаксисом подобных проблем можно легко избежать. Так ясно?

Ок. Вообще, гомоиконность это когда вызов макроса не отличается от вызова функции? Мог бы быть haskell/ml-подобный язык гомоиконным? Если в нём вызов макроса отличается от вызова функции видом скобок, например (пусть - {foo ... code ... [code ...] ...} вместо (foo ... code ... (code ...) ...), вокруг foo - засахаренный код, внутри foo - засахаренный код, цитируемый с помощью [...]).

Вот в том и дело, что «по назначению» в ЯП с гомоиконным синтаксисом - это на порядок более широкое множество задач, чем «по назначению» в ЯП с негомоиконным синтаксисом. Уже хрен знает сколько пытаюсь донести до тебя эту мысль, но никак не доходит.

Вот опять слова о превосходстве одного инструмента над другим.

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

При этом, есть пишущие лисперы, которые не разделяют такую точку зрения (против «писать макросы на каждый чих»).

Который от xml/xslt через cl пришел к православности js и пропагандирует web-технологии как вершину ui-строительства?

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

В конце вот этого твоего поста опять есть намёк на то что он, по крайней мере, «лучше», этот подход.

Какой еще подход? Не шла речь нигде ни о каких подходах, речь шла об одном конкретном инструменте и о том, что в определенных ситуациях (ЯП с гомоиконным синтаксисом) инструмент намного более эффективен, чем в других. Это ты себе понапридумывал уже какие-то подходы и прочую чепуху.

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

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

Пошёл посмотрел - 40 знаков.

При чем тут знаки? В твоем коде нельзя понять во что он вообще раскрывается, не выполнив его в уме, потому что АСТ собирается вручную, а не декларативно (то есть вместо того, чтобы описать результирующий АСТ мы описываем процесс его построения и заранее результат процесса не знаем).

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

Это, конечно, не факт, что они более высокоуровневые чем что-то другое, но пока что никаких претендентов на хотя бы равное по уровню средство никому неизвестно (речь о средствах для написания макросов, естественно, а не о каких-то других, не имеющих никакого отношения к теме).

То что я там описал - это чисто ML-ный подход к вещам, макросы тут вообще ни при чём.

Что значит «макросы не при чем», если речь шла _только_ о макросах и ни о чем больше? Как этот ML-ный подход к вещам относится к теме разговора, и, раз не относится, то зачем он вообще был упомянут?

При этом, есть пишущие лисперы, которые не разделяют такую точку зрения (против «писать макросы на каждый чих»).

Конечно. В CL трудно писать макросы на каждый чих, потому что они там кривые (АСТ руками, да, один в один как в TH). Соответственно, и юзкейсы более близки к юзкейсам TH. Ну а поскольку большая часть лисперов - это как раз общелисперы, и вообще так негласно считается, что лисп - это общелисп, то нутыпонелда.

Ок. Вообще, гомоиконность это когда вызов макроса не отличается от вызова функции?

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

Мог бы быть haskell/ml-подобный язык гомоиконным?

Мог бы, конечно.

Вот опять слова о превосходстве одного инструмента над другим.

Еще раз, мы имеем два инструмента - макросистема в гомоиконном ЯП и макросистема в негомоиконном ЯП (по крайней мере я рассматриваю именно эти два, чего ты там рассматриваешь я не в курсе). И ты _сам_ лично указал, что макросистема в негомоиконном ЯП применима весьма ограничено (и в куче юзкейсов мы, там где можно было бы использовать макросы, применяем вместо них альтернативные инструменты типа АТД, ФВП и так далее), а теперь ты же и споришь с этим фактом? Определись уже.

anonymous
()
Ответ на: комментарий от vladimir-vg

Io, Rebol, Factor

И сабж, как заявлено (полноценная система гигиенических макросов). Надо будет посмотреть. Многого, конечно, не жду.

quasimoto ★★★★
()

На мой взгляд язык совсем не соответствует своему же названию. Для llvm нет ничего лучше ocaml :)

nCdy
()
Ответ на: комментарий от anonymous

речь шла об одном конкретном инструменте и о том, что в определенных ситуациях (ЯП с гомоиконным синтаксисом) инструмент намного более эффективен, чем в других.

Тогда ладно, но если, как было сказано, ЯП с конкретным синтаксисом (вроде ML/Haskell) может быть, в принципе, гомоиконным, то изначальный наезд на инфикс был некорректным.

Что значит «макросы не при чем», если речь шла _только_ о макросах и ни о чем больше? Как этот ML-ный подход к вещам относится к теме разговора, и, раз не относится, то зачем он вообще был упомянут?

Это был пример реальной задачи (конфигурирование работающей системы с помощью внешних файлов конфигурации, написанных на неком DSL), который в лиспах естественно решается с помощью макро-системы, но в ML-ях естественно решается другими средствами, без надобности привлечения макро-системы.

Нет, это когда сам код является АСТ.

Вот с точки зрения TH хаскельный код _является_ AST. По какой причине TH это не гомоиконная система тогда? Высокоуровневые define syntax rules это уже следствие для гомоиконного языка. Причина - гомоиконность. Если способ вызова макросов не важен - почему TH нельзя считать гомоиконным?

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

Тогда ладно, но если, как было сказано, ЯП с конкретным синтаксисом (вроде ML/Haskell) может быть, в принципе, гомоиконным, то изначальный наезд на инфикс был некорректным.

Ну да, если взять ЯП с негомоиконным синтаксисом и поменять его на гомоиконный (что предполагает исключение инфикса), то ЯП станет гомоиконным. Но с какой стати наезд некорректен?

Это был пример реальной задачи (конфигурирование работающей системы с помощью внешних файлов конфигурации, написанных на неком DSL), который в лиспах естественно решается с помощью макро-системы, но в ML-ях естественно решается другими средствами, без надобности привлечения макро-системы.

Ну и какое это имеет отношение к теме разговора?

Вот с точки зрения TH хаскельный код _является_ AST.

Не является, конечно же. Какой АСТ задает код «1 op 2 op 3», если неизвестно, что такое op (то есть это может быть оператор любой ассоциативности, или функция или вообще константа)? Для того, чтобы код являлся АСТ нужно, как минимум, прямое соответствие.

По какой причине TH это не гомоиконная система тогда?

По той причине, что хаскельный код не является АСТ.

Высокоуровневые define syntax rules это уже следствие для гомоиконного языка. Причина - гомоиконность.

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

Если способ вызова макросов не важен - почему TH нельзя считать гомоиконным?

Потому что код отделен от АСТ. Вот возьмем выражение ((+) 1 2), ему в хаскеле соответствует нечто вроде (App (Function +) (Lit 1) (Lit 2)). Чтобы хаскель был гомоиконным ЯП, код, представляющий АСТ, должен совпадать с АСТ, то есть АСТ (App (Function +) (Lit 1) (Lit 2)) должно задаваться кодом "(App (Function +) (Lit 1) (Lit 2))". Для лиспа это правило выполняется, соответственно, лисп - гомоиконный ЯП. А хаскель - нет.

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

И сабж, как заявлено

Нигде не было заявлено, что сабж гомоиконный. А если бы и было - это было бы очевидной ложью :)

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

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

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

Давно ждал, чтобы кто-то это сказал.

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

Какой АСТ задает код «1 op 2 op 3», если неизвестно, что такое op (то есть это может быть оператор любой ассоциативности, или функция или вообще константа)?

А такого кода в хаскеле и не может быть (синтаксически невалидная конструкция). Может быть (op (op 1 2) 3), (op 1 (op 2 3)) или (1 `op` 2 `op` 3).

Чтобы хаскель был гомоиконным ЯП, код, представляющий АСТ, должен совпадать с АСТ, то есть АСТ (App (Function +) (Lit 1) (Lit 2)) должно задаваться кодом "(App (Function +) (Lit 1) (Lit 2))".

Ну и это уже лисп. Интересно про гомоиконный не лисп.

Если представления кода и AST изоморфны, и есть возможность _из языка_ использовать функции задающие изоморфизм, т.е. quote : код -> AST и splice : AST -> код, то это и будет гомоиконный язык. Если при этом представления кода и AST совпадают, то это уже лисп.

У хаслеля - представление кода (которое недоступно, например, HsExpr в GHC) не совпадает с представление AST (Exp из TH), так что это не лисп. Более того, представление AST не замкнуто - с помощью TH можно разбирать / собирать хаскель-код, но нельзя собирать TH код, т.е. в Exp, Dec, Type и т.п. из TH нет конструкторов для самого TH. Так что TH не гомоиконная система.

При этом сами представления кода - HsExpr в GHC или Exp из haskell-src-exts, - замкнуты относительно конструкций TH, т.е. то как TH реализовано в GHC - оно там просто недореализовано до (формальной) гомоиконности.

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

Если при этом представления кода и AST совпадают, то это уже лисп.

* и являются s-выражениями.

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

Давно ждал, чтобы кто-то это сказал.

Я тебе более того скажу: у нас конфигурация, если её так можно назвать, которая светится кастомеру, была переписана с s-выражений на тупой вендовый ini. Ибо ваять поделие для s-выражений, полностью устойчивое к дуракам, нисколько не проще, чем написать валидирующий парсер ini, от формата которого у жалких человечишек баттхёрта не возникает.

Жаль, конечно - раньше одним read'ом макрочудеса неописуемые творились.

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

А такого кода в хаскеле и не может быть (синтаксически невалидная конструкция). Может быть (op (op 1 2) 3), (op 1 (op 2 3)) или (1 `op` 2 `op` 3).

Ну не суть важно, пусть будет (1 `op` 2 `op` 3)

Ну и это уже лисп.

Нет, это не лисп, это ЯП с гомоиконным синтаксисом.

Интересно про гомоиконный не лисп.

Ну придумай гомоиконный не-лисп, кто тебе мешает? :)

Если представления кода и AST изоморфны, и есть возможность _из языка_ использовать функции задающие изоморфизм, т.е. quote : код -> AST и splice : AST -> код, то это и будет гомоиконный язык.

Нет, если существует изморфизм и есть взможность использовать задающие его ф-и - то это _любой_ ЯП. Потому что в любом ЯП есть парсер, который по коду строит АСТ, задавая подобный изоморфизм. И в любом ЯП мы можем реализовать этот парсер в виде отдельной библиотеки, которая позволит строить АСТ из этого языка. Но с гомоиконностью это, конечно, ничего общего не имеет.

Если при этом представления кода и AST совпадают, то это уже лисп.

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

Более того, представление AST не замкнуто - с помощью TH можно разбирать / собирать хаскель-код, но нельзя собирать TH код, т.е. в Exp, Dec, Type и т.п. из TH нет конструкторов для самого TH. Так что TH не гомоиконная система.

Вот этого я не знал. Этот момент как раз относится к вызовам макросов - если макровызовы соответствуют функциональным, то замкнутость получается автоматом. Но я гомоиконность все же понимаю в описанном выше смысле (то есть «код и данные имеют одинаковое представление» - причем это и есть общепринятое определение) - и там тоже замкнутость получается автоматом.

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

Ну не суть важно, пусть будет (1 `op` 2 `op` 3)

Тогда [| 1 `$op` 2 `$op` 3 |] - в текущем TH не работает, но это технический issue. Расстановка скобок аппликаций в хаскеле лево-ассоциативна - a b c == ((a b) c), a `f` b `f` c == ((a `f` b) `f` c) (f - аргумент). Короче, InfixE - часть AST, также как LetE или TupE - разные [| let x = $a ; y = $b in $z |] и [| ($a, $b) |] ведь работают, хотя тут миксфикс и инфикс. Вложенные вещи вроде [| ... $( ... [| ... |] ... ) ... |] тоже работают.

Нет, если существует изморфизм и есть взможность использовать задающие его ф-и - то это _любой_ ЯП.

«Функции задающие изоморфизм» это в математическом смысле (изоморфизм множеств это две таких-то функции, изо-стрелка это две таких-то стрелки и т.п.), с точки зрения языка это будет часть AST, т.е. у типов Exp и ExpMeta будут конструкторы

Quote :: Exp -> ExpMeta
Splice :: ExpMeta -> Exp? -> Exp

это часть грамматики, которая также требует себе семантических атрибутов (по сути, реализации expand-ера).

Нет, это как раз и есть гомоиконный ЯП.

Точно, ExpMeta = Exp - гомоиконный. Если при этом ещё Exp = SExp, то лисп (splice редуцируется в аппликацию, остаётся только quote).

Для меня вопрос существования гомоиокнного не-лиспа, вообще говоря, открыт.

Тут упомянули Io, Rebol и Factor. Ещё есть Dylan (CL с обрезанными ногтями :)), в Nemerle и F# ещё что-то есть. Но для меня тоже открыт вопрос.

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

Ну вот возьмём

data Expression
  = Lit Literal
  | Var Variable
  | Lam [Variable] Expression
  | App Expression [Expression]
  -- sugar, sugar, etc.
  | Quote Expression
  | Splice Expression [Expression]

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

expand :: Expression -> Expression
eval :: Expression -> Expression
-- ^ Связаны мета-циклически.

Expression замкнут, но уже не s-выражения, мета-циклическая пара eval и expand присутствует. Синтаксическая форма <quote> <expr> при этом даст _объект_ AST, с которым можно будет совершать любые действия в самом языке (в духе аккессоров и конструкторов, но уже не car, cdr и cons, а тех, которые задаются этим ADT, более высокоуровневое конструирование и паттерн-матчинг тоже надстраиваются).

Вообще, с чему бы типу обычных деревьев играть какую-то выделенную роль, тип более богатый конструкторами, в принципе, должен являться обобщением, позволять замыкание относительно quote/splice и возможность реализовать мета-циклические expand/eval.

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

но уже не car, cdr и cons, а тех, которые задаются этим ADT

В лиспах car, cdr, cons и некоторые другие функции для s-выражений это примитивы, так что в этом примере конструкторы, аккессоры и прочие необходимые функции связанные с Expression станут примитивами языка.

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

типу обычных деревьев

Хотя, там точечные пары, которыми представляются кроме деревьев ещё и некоторые графы-не-деревья. Но когда ими представляется код, а не данные, то это субтип - s-выражения, деревья символов.

Есть одна старая статья - критика SICP на тему использования точечных пар для целей представления ADT (утверждается, что в ML всё няшне). Конечно, в R6RS и CL появились нормальные структуры, объединения, конструкторы, аккессоры и возможность написать компилятор паттерн-матчинга на макросах, но AST всё ещё кодируется s-выражениями ;)

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

Тогда [| 1 `$op` 2 `$op` 3 |] - в текущем TH не работает

Nuff said.

Расстановка скобок аппликаций в хаскеле лево-ассоциативна - a b c == ((a b) c), a `f` b `f` c == ((a `f` b) `f` c) (f - аргумент).

И что произойдет, когда окажется, что f - правоассоциативен?

«Функции задающие изоморфизм» это в математическом смысле (изоморфизм множеств это две таких-то функции, изо-стрелка это две таких-то стрелки и т.п.), с точки зрения языка это будет часть AST, т.е. у типов Exp и ExpMeta будут конструкторы

Ну да. И любой ЯП имеет этот изоморфизм и соответствующие функции, которые можно использовать изнутри языка. следовательно, согласно твоему определению любой ЯП - гомоиконный.

то лисп (splice редуцируется в аппликацию, остаётся только quote).

Куда он редуцируется и с какой стати?

Тут упомянули Io, Rebol и Factor. Ещё есть Dylan (CL с обрезанными ногтями :)), в Nemerle и F# ещё что-то есть. Но для меня тоже открыт вопрос.

Ну Dylan, Nemerle и F# явно негомоиконные, про IO с Rebol'ом ничего сказать не могу, разве что Factor остается.

Вообще, с чему бы типу обычных деревьев играть какую-то выделенную роль, тип более богатый конструкторами, в принципе, должен являться обобщением, позволять замыкание относительно quote/splice и возможность реализовать мета-циклические expand/eval.

Угу, но только это все никак не связано с гомоиконностью. Вот в том же Racket, например, АСТ представляется в виде syntax object, а не в виде списков - важно, что эти syntax object полностью наследуют структуру кода (то есть являются надстройкой над списками, благодаря чему все, что можно делать со списками, искаробки можно делать и с syntax object точно теми же способами), а какие там аксесоры и прочая чушь - это не важно, это просто теоретические моменты, которые на практике ничего не значат.

Expression замкнут, но уже не s-выражения, мета-циклическая пара eval и expand присутствует. Синтаксическая форма <quote> <expr> при этом даст _объект_ AST, с которым можно будет совершать любые действия в самом языке (в духе аккессоров и конструкторов, но уже не car, cdr и cons, а тех, которые задаются этим ADT, более высокоуровневое конструирование и паттерн-матчинг тоже надстраиваются).

Понимаешь, ты говоришь о теоретической возможности, а я о практическом использовании. Я пытаюсь тебе объяснить, что если задачу можно решить не менее чем за сто миллионов лет, то на практике это ничем не отличается от задачи, которую нельзя решить вовсе, ты же мне в ответ говоришь - но ведь теоретическое решение есть, expand/eval наличествуют, надстроить теоретически можно бла бла бла...

eval :: Expression -> Expression

eval :: Expression -> Datum - вот так.

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

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

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

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

но AST всё ещё кодируется s-выражениями

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

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

И что произойдет, когда окажется, что f - правоассоциативен?

infixr 5 `f`

будут справа скобки. В случае если f - аргумент, а не top-level функция:

foo f a b = a `f` b

тогда слева.

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

Это не функции, это Quote / Splice в виде конструкторов AST.

Куда он редуцируется и с какой стати?

В AST нет конструктора splice. Есть quote. splice это просто apply когда голова - символ макроса.

а какие там аксесоры и прочая чушь

ADT (~ AST у нас тут) и его поведение задаётся сигнатурами конструкторов, задаёт поведение и сигнатуры аккессоров. Это ключевые моменты. Но тем, кто не любит думать в data driven терминах так не кажется, я смотрю :)

eval :: Expression -> Datum - вот так.

Нет, eval замкнут. Datum, или Value, содержится в Expression. Например, literals, lambdas, quotes - values в expression, applications и variables - нет. Вынести, т.е. агрегировать, values при наличии конструктора quote не получится (да это и просто неудобно / не нужно).

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

Я пытаюсь тебе объяснить

Ты демонстрируешь, что после того как у кого-то паттерн Compiler вдруг стал естественной языковой фичей (макросы), акцент разработки вдруг несимметричным образом сместился в сторону этого паттерна. Иногда ценой игнорирования других паттернов и концептов.

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

Вынести, т.е. агрегировать, values при наличии конструктора quote не получится

Нет, ну можно, но Value и Exp будут взамо-рекурсивными типами. Не нужно.

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

будут справа скобки.

Так где они будут после построения АСТ (то есть до того, как мы узнали ассоциативность)?

Это не функции, это Quote / Splice в виде конструкторов AST.

Конструкторы - это функции. И, кстати, Quote/Eval, а не Quote/Splice.

В AST нет конструктора splice. Есть quote. splice это просто apply когда голова - символ макроса.

Ну он есть, просто вырожден. Так ведь и quote в данном случае вырожден, значит следует считать, что его нет.

ADT (~ AST у нас тут) и его поведение задаётся сигнатурами конструкторов, задаёт поведение и сигнатуры аккессоров. Это ключевые моменты.

AST задается его грамматикой, а в виде каких ADT оно будет представлено - это дело десятое, ведь всегда можно сделать враппер к другому эквивалентному представлению. Так что здесь следует говорить о классе представлений АДТ, который соответствует данной грамматике. Выбор конкретного представителя класса ничего не меняет - как и всегда при выборе одного объекта из совокупности изоморфных :)

Нет, eval замкнут.

Конечно замкнут, но тип у него Expression -> Datum, иначе это не евал, а какая-то профанация.

Datum, или Value, содержится в Expression.

Нет, наоборот - Expression содержится в Datum. То есть код - некоторый частный случай данных, не наоборот.

Вынести, т.е. агрегировать, values при наличии конструктора quote не получится (да это и просто неудобно / не нужно).

коненчо, не получится. Метацикличность нельзя формально выразить :)

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

Ты демонстрируешь, что после того как у кого-то паттерн Compiler вдруг стал естественной языковой фичей (макросы), акцент разработки вдруг несимметричным образом сместился в сторону этого паттерна. Иногда ценой игнорирования других паттернов и концептов.

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

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

Метацикличность нельзя формально выразить

Это безусловно очень важное преимущество Лиспа, ведь оно так повышает ЧСВ лиспокодера.

stack_protector
()
Ответ на: комментарий от anonymous

Так где они будут после построения АСТ (то есть до того, как мы узнали ассоциативность)?

Попробую так - если у нас функция отдаётся аргументом, то никакой ассоциативности у неё быть не может (поэтому те штуки в TH не работают), для неё будет простой AST с AppE. Писать [| a `f` b |] можно только для объявленных функций (возможно, указана ассоциативность), [| a ? b |] - для объявленных операторов (тоже).

Конструкторы - это функции. И, кстати, Quote/Eval, а не Quote/Splice.

Это особого рода функции (у них единственное правило rewriting - identity). Может, я вообще считаю за функции не identiчные rewriting rules. Нет не eval, почему? Eval как раз обычная функция, это _точно_ не конструктор (сам посуди). В лиспах нету аналога конструктора splice.

Так ведь и quote в данном случае вырожден

Откроем любой стандарт - спец. форма quote есть, самая обычная, ни во что не вырожденная.

но тип у него Expression -> Datum

Пусть так.

Нет, наоборот - Expression содержится в Datum

Они взаиморекурсивны - содержатся в друг друге (агрегируют друг друга).

data Val
  = Lit Lit
  | Lam [Exp] Exp
  | Quote Exp

data Exp
  = Var Var
  | Val Val
  | App Exp [Exp]
  | Splice Exp [Exp]
quasimoto ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.