LINUX.ORG.RU

Макросы для нескольких S-expressions


2

1

Интересует вопрос, можно ли в диалектах Lisp писать макросы, которые преобразуют не один S-expression, находя его по первому элементу списка, а позволяют указать более сложный шаблон, по которому искать дерево для трансформации?

Чтобы было понятнее, пример на псевдоязыке:

(defmacro (swap-begin _* :as contents swap-end)
  (reverse contents))

Такой макрос по задумке должен выдать все S-exp между swap-begin и swap-end в обратном порядке:

=> (macroexpand '(swap-begin (+ 1 2) (println "whatever") (/ 2 1) swap-end))
((/ 2 1) (println "whatever") (+ 1 2))

Извиняюсь, если получилось немного сумбурно; надеюсь, общая идея понятна.

У самого есть подозрение, что нужно глубже гуглить по словам reader macro или code walking, но не уверен (я пока только начинаю знакомиться с Lisp).

★★★★

Никто не мешает в макросе всё, что идёт после названия макроса, интерпретировать как угодно. В том числе ждать появления :swap-end или чего-нибудь подобного.

PolarFox ★★★★★
()

которые преобразуют не один S-expression, находя его по первому элементу списка,

Ну у тебя он увидел 'swap-begin как первый элемент списка и подействовал как тебе надо. Или что ты имеешь в виду?

Конкретно твой пример в CL можно выполнить с помощью обычного макроса

(defmacro inverse ((begin end) &body body)
  (let ((begin-pos (position begin body))
        (end-pos (position end body)))

    (concatenate 'list
                 (subseq body 0 (1+ begin-pos))
                 (reverse (subseq body (1+ begin-pos) end-pos))
                 (subseq body end-pos))))
onanij
()

Смотри на макрос loop в CL, например, как на образчик сильного колдунства.

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

swap-begin я поставил в начале списка, просто, чтобы упростить пример. Более сложный вариант:

(defmacro (_ :as begin (_ _ _) _ :as end)
  (begin ('one 'two 'three) end))

Макрос преобразует все списки из трёх элементов, где второй элемент сам является списком из трёх элементов:

=> (macroexpand '(a (1 2 3) c))
(a (one two three) c)

=> (macroexpand '(a (1 2) c))
(a (1 2) c)

Интересует именно теоретическая возможность.

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

Интересует именно теоретическая возможность.

1) В Racket развитый паттерн-матчинг для макросов.

2) Для CL существуют библиотеки для паттерн-матчинга.

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

Давай тогда пример, как это сделать на CL

Псевдокод:

(new-super-puper-macro (_ (a b c) _) (_ (c b a) _)

(a (1 2 b) c) раскрылось бы в (c (b 2 1) a)

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

Он не должен никак отреагировать, т.к. данное выражение не попадает под pattern макроса (внутренний список содержит два элемента вместо трёх), что и было показано в примере.

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

Он не должен никак отреагировать, т.к. данное выражение не попадает под pattern макроса (внутренний список содержит два элемента вместо трёх), что и было показано в примере.

В смысле? У тебя вызов макроса раскрылся в самого себя, при компиляции такого вызова получим бесконечную рекурсию.

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

Откуда грибочки? Походу ты немного не понял, что хочет ТС.

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

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

Если ты про первый пример, то да, немного ступил, но это легко поправить:

(defmacro (_ :as begin (_ _ _) _ :as end)
  (begin ('one 'two 'three 'four) end))


=> (macroexpand '(a (1 2 3) c))
(a (one two three four) c)
runtime ★★★★
() автор топика
Ответ на: комментарий от korvin_

Опять, с точки зрения CL

If form is a macro call, then macroexpand-1 will expand the macro call once and return two values: the expansion and t. If form is not a macro call, then the two values form and nil are returned.

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

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

Не, он про второй. Там в списке 2 элемента было, шаблон не сработал, и вернулось выражение в неизменном виде. Все верно у тебя

onanij
()
Ответ на: комментарий от onanij
(defun reverse-tree (xs)
  (if (listp xs)
      (reverse (mapcar #'reverse-tree xs))
      xs))

(defmacro c (&whole form &rest _)
  (declare (ignore _))
  (reverse-tree form))

(defmacro a (lst end)
  (handler-case (destructuring-bind (_ _ _) lst
                  (declare (ignore _))
                  `(a (one two three) ,end))
    (error (e) `(a ,lst ,end))))
(macroexpand-1 '(a (1 2 3) c))
; => (A (ONE TWO THREE) C)

(macroexpand-1 '(a (1 2) c))
; => (A (1 2) C)

(macroexpand-1 '(c (1 2 3) b))
; => (B (3 2 1) C)

Я все еще не понимаю, в чем именно проблема.

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

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

Зачем? Чтоб тот, кто потом будет читать код, поломал себе мозг насовсем?

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

Не, он про второй. Там в списке 2 элемента было, шаблон не сработал, и вернулось выражение в неизменном виде. Все верно у тебя

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

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

У тебя тут 2 макроса - a и c.

У ТС - это ЛЮБЫЕ 2 символа, не обязательно связанные с какими-либо функциями или макросами.

Т.е. это работает и с (lol (d g h) o), и с (foo (1 2 3) bar), и с (1 (2 3 4) 5), но не работает с (y (1 2) fg)

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

Зачем?

Даже не спрашивай.

Чтоб тот, кто потом будет читать код, поломал себе мозг насовсем?

Очевидно, так и будет

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

Я все еще не понимаю, в чем именно проблема.

В том, что тебе необходимо явно писать макросы для 'a и 'c. Хотелось бы, чтобы макрос срабатывал для любой формы, которая попадает под шаблон.

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

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

Забей на зацикливание, это была проблема данного конкретного примера.

Зачем? Чтоб тот, кто потом будет читать код, поломал себе мозг насовсем?

Нет, пока просто интересует теория.

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

Зачем? Чтоб тот, кто потом будет читать код, поломал себе мозг насовсем?

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

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

Хотелось бы, чтобы макрос срабатывал для любой формы, которая попадает под шаблон.

Лучше расхоти.

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

Ну если бы такое и было, то можно было бы ограничить действие макроса (как in-package действует ограничено, так и эти тоже)

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

Я ен знаю, можно ли ограничивать изменения read-table пакетом, если да, то проблем вообще никаких, чуть-чуть изменить reader-macros для #\( и все.

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

Вообще, таки да. Что-то я не подумал, что для #\( стоит обычный reader macros.

можно ли ограничивать изменения read-table пакетом

Кривой вариант: можно написать обертки вокруг load/compile-file, итд, чтобы связывали на время вызова некую динамическую переменную *active-patterns*, где хранить шаблоны. Тогда для каждого файла с исходниками будут свои шаблоны

onanij
()

я пока только начинаю знакомиться с Lisp

Если не секрет, зачем? Сириусли, интересно же.

anonymous
()

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

Ты хочешь какую-то херню.

no-such-file ★★★★★
()

В принципе идея похожа на то, как реализуется синтаксис iterate. (for x in-vector a from 1 below 10) --- здесь for, in-vector, from, below --- ключевые слова, остальное биндится во время отработки макроса. Можешь посмотреть как там сделано и творчески доработать. Но это скорее должен быть расширенный destructuring-bind, чем некий преобразователь, так как матчинг S-выражения и обработка его --- это два ортогональных понятия.

anonymous
()

более сложный шаблон, по которому искать дерево для трансформации

Вообще говоря, это не lisp-way. Советую посмотреть устройство loop, но, в любом случае, это не lisp-way.

mix_mix ★★★★★
()
Ответ на: комментарий от no-such-file

Ты хочешь какую-то херню.

Ну почему же... Можно будет потом делать типа:

(in-readtable :imit-c)

(i : int)
(j : int)

(for i := 1 to 10 
  (for i := 1 to 10 
    (printf i + j)))

^_^

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

А если этим «макросам» добавить в шаблон не только скобки а любые символы, то вообще можно в исходном коде скобки повыкидывать... В общем, смотри http://docs.racket-lang.org/algol60/

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

Ага, кому бы оно только было надо. В лиспе вообще недостаток синтаксического сахара. Я на память вспомню только ', `, @. А где generator expression, как в пистоне? Где обращение к элементу массива как в c? Ну и прочее.

Или вот ещё супер-пупер «фича»:

(defun foo (a _) (bar a 4)) -> (defun foo (a b) (declare (ignore b)) (bar a 4))

ТС мог бы для прикола запилить штуку, которая всё это реализует.

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

это ни хера не любопытно, попыток избавить лисп от скобок было больше, чем звёзд на небе, и на данном этапе этапе за них нужно банить, как когда-то Парижская академия не принимала метеориты - не при нынешнем уровне развития ;)

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