> 1 <

Автор Сообщение

Яков Замир Кацман (нью)

Members


Статус

22 сообщений

Где: Russia
Род занятий:
Возраст:

#7796   2017-11-06 13:25 GMT+3 часа(ов)      
;;  *All what you need know about macros in lisp*
 
;;Like a function, a macro consists of a name, a parameter list, an optional
;;documentation string, and a body of Lisp expressions.However, as I just
;;discussed, the job of a macro isn't to do anything directly­­its job is to
;;generate code that will later do what you want.
;; [0] http://dorophone.blogspot.ru/2008/03/common-lisp-reader-macros-simple.html
;; [1] http://www.gigamonkeys.com/book/a-few-format-recipes.html
;; [2] https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node51.html
;; (L) J.Z.Katsman (2017) list.development@gmail.com | jzkatsman @ jabber.ru
 
;; define macros
(defmacro when-condition (condition &rest body)
`(if ,condition (progn ,@body)
(format t "~% a is more than ~a" (nth 2 '(,@condition))))
)
 
;; assingment variable
(setq a 220)
220
;; call macro statement
(when-condition (< a 10) (princ "little number"))
 
;; call macro expand routine
(macroexpand-1 '(when-condition (< a 10) (princ "little number")))
(IF (< A 10) (PROGN (PRINC "little number")) (FORMAT T "~% a is more than 10"))
 
;; Macros build construction explaining. Сonsist from 2 elements
;; First !
CL-USER 5 > (setq b '(1 2 3))
(1 2 3)
 
CL-USER 6 > `(a ,@b c)
(A 1 2 3 C)
 
;; Second!
;; Commas work no matter how deeply they appear
;; within a nested list:
CL-USER 7 > (setf a 1 b 2 c 3)
3
 
CL-USER 8 > `(a ,b c)
(A 2 C)
 
CL-USER 9 > `(a (,b c))
(A (2 C))
 
;; Вложенные макросы можно посмотреть в развернутом состоянии/виде
;;Expand a macro form completely
;;[1] https://stackoverflow.com/questions/16579844/expand-a-macro-form-completely
 
(setf *print-pretty* t)
(defmacro lv2() `(+ 2 3))
(defmacro lv1() `(+ 1 (lv2)))
(compile 'lv1)
(macroexpand-1 '(lv1))
(lv1)
 
 
;;SLIME: M-x slime-macroexpand-all with C-c M-m
;;LispWorks: menu Expression > Walk or M-x Walk Form, shorter M-Sh-m.
 
;;Work -> Expression -> Walk
;;-----------------
;;(IN-PACKAGE "COMMON-LISP-USER")
;;(+ 1 (+ 2 3))
;;-----------------------
 
 
;; Yours sincerely, J.Z.Katsman / Enjoy folks!

отредактировал(а) Яков Замир Кацман (нью): 2017-12-11 01:03 GMT+3 часа(ов)

Яков Замир Кацман (нью)

Members


Статус

22 сообщений

Где: Russia
Род занятий:
Возраст:

#7812   2017-12-03 16:20 GMT+3 часа(ов)      
;; Простые макросы.
 
(defun function10(x)
(let ((y 1))
(+ y x))
)
 
(defmacro macros10(x)
(let ((y (gensym)))
`(let ((,y 1))
(+ ,y ,x)))
)
 
;; вызывали
(let ((y 1)) (macros10 y))
 
;; тут можно развернуть!
(macroexpand '(macros10 y))
(LET ((#:G780 1)) (+ #:G780 Y))
 
 
;; вызывали
(let ((y 1)) (macros10 y))
;; вызывали
(let ((y 1)) (function10 (+ 1 2 3 4 5)))
;; вызывали
(let ((y 1)) (macros10 (+ 1 2 3 4 5)))
 
 
;;Весь секрет макросов. Функция с eval?
(defun foo0 (x)
(eval (list 'exp x))
)
 
(defun foo1 (x)
(list 'exp x))
 
(defmacro foo2 (x)
(list 'exp x))
 
(defmacro foo3 (x)
`(exp ,x))
 
 
(foo1 0)
2.7182817
;;функция вычисляет список
 
(foo1 1)
(EXP 1)
;;функция возвращает список
 
(foo2 1)
2.7182817
;;макрос вычисляет список

отредактировал(а) Яков Замир Кацман (нью): 2017-12-03 16:29 GMT+3 часа(ов)

skelter

Members


Статус

40 сообщений

Где: ---
Род занятий:
Возраст:

#7813   2017-12-05 00:49 GMT+3 часа(ов)      
> ;;Весь секрет макросов. Функция с eval?

Нет, макросы гораздо интересней. eval просто вычисляет выражение в рантайме без лексических связываний. Макрос раскрывается при компиляции, при этом порождённый код не обязан быть вычисляемым выражением. Кроме того, порождённый код живёт в лексической среде (макрос сам не знает в каких средах он может раскрываться!), и семантика порождённого кода может быть разная в разных средах. Видимо, поэтому стандарт и запрещает переопределять символы из пакета COMMON-LISP — чтобы хоть что-то было гарантировано при раскрытии макроса.

Яков Замир Кацман (нью)

Members


Статус

22 сообщений

Где: Russia
Род занятий:
Возраст:

#7814   2017-12-05 14:16 GMT+3 часа(ов)      
Киньте пожалуйста ссылку на источник (если вас не затруднит).
Очень заинтересовала "семантика порождённого кода". Если вы позволите
у меня будет еще несколько вопросов (чуть позже), это все очень интересно.
Еще лучше бы здесь каких-нибудь маленьких примеров. Вроде таких: *http://lisper.ru/articles/cl-vars*
Интересует, что можно сделать макросами, но нельзя никаким другим способом. Уникальные возможности макросов.

отредактировал(а) Яков Замир Кацман (нью): 2017-12-05 14:37 GMT+3 часа(ов)

skelter

Members


Статус

40 сообщений

Где: ---
Род занятий:
Возраст:

#7815   2017-12-07 21:01 GMT+3 часа(ов)      
Ну, насчёт eval написано в CLHS — динамические переменные из него видны, а лексические — нет. А вообще что я про макросы читал - не помню. В принципе, On Lisp и Let Over Lambda - кусками то и другое. Я, конечно, не против пообсуждать - с оговоркой, что по макросам я не эксперт и вообще, на лиспе пишу на уровне хобби.

По поводу того, что макрос порождает форму, не задумываясь о его смысле — это просто определение макроса. Вот это: «порождённый код не обязан быть вычисляемым выражением» — я криво написал. Имеется в виду вот что.

Вот я напишу (foo bar baz) — это осмысленное выражение или нет? Зависит. Если просто открыть свежий лисп и забить в REPL, конечно, будет ошибка: функция не определена, переменные не определены. А если загрузить библиотеку, в которой определена функция foo и глобальные переменные bar и baz, это выражение уже имеет смысл.

Но даже необязательно загружать библиотеку. Это выражение может встретиться в глубине кода — и в том месте символы foo, bar и baz могут иметь какое-то значение. Например,
(flet ((foo (&rest args)
(apply #'+ args)))
(let ((bar 1)
(baz 2))
(foo bar baz)))
 

или даже
(macrolet ((foo (bar baz)
(let ((counter (gensym)))
`(do ((,counter 0 (1+ ,counter))
(,bar ,bar))
((= ,counter ,bar) nil)
(funcall ,baz)))))
(let ((bar 3)
(baz (let ((i 0))
(lambda ()
(format t "Привет мир № ~D.~%" (incf i))))))
(foo bar baz)))
Привет мир № 1.
Привет мир № 2.
Привет мир № 3.
NIL

В первом случае foo стало именем функции, а bar и baz — переменными. Во втором foo превратилось в управляющую конструкцию (цикл), который три раза вызвал функцию baz.

Можно определить макрос
(defmacro qux ()
'(foo bar baz)

и вызовы
(flet ((foo (&rest args)
(apply #'+ args)))
(let ((bar 1)
(baz 2))
(qux)))
 

и
(macrolet ((foo (bar baz)
(let ((counter (gensym)))
`(do ((,counter 0 (1+ ,counter))
(,bar ,bar))
((= ,counter ,bar) nil)
(funcall ,baz)))))
(let ((bar 3)
(baz (let ((i 0))
(lambda ()
(format t "Привет мир № ~D.~%" (incf i))))))
(qux)))

абсолютно эквивалентны предыдущим. Макрос qux порождает список (foo bar baz), и вычисление макроса превращается в вычисление этого списка.

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

Вот так - концептуально очень просто! - работают макросы в CL. Код в CL состоит из списков (из форм, точнее говоря - ведь, например, символ не список), и макрос преобразует свои аргументы в список, который рассматривается как код.

Как говорят, макрос - это программа, пишущая программу. Хойт в книге Let Over Lambda делает сильный акцент на том, что лисп - программируемый язык программирования и предлагает программисту на лиспе сосредоточиться именно на этом аспекте языка. Вот, например, он пишет:
Цитата
some lisp programmers try to always write macros, extending the lexical context as far as possible, using a function only when they need to evaluate arguments or just chicken out and want a new lexical context.

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

Правда, в этом случае, даже хотя порождаемая программа имеет такое удобное и понятное представление как список, нам приходится рассматривать её как результат. Мы не привыкли. Как её моделировать? Как гарантировать, что она будет адекватно работать? Хойт пишет, что никто ещё не знает, на что способно метапрограммирование. Науке неизвестно, терра инкогнита. Можно ещё посомневаться насчёт того, как порождающую и порождаемую программы дебажить, как поддерживать. То есть в конкретном случае нужно понять, стоит ли так делать. (И житейская мудрость «если можно сделать функцией, не пиши макрос» убеждает, что не стоит.) Однако в лиспе - можно.

Если метапрограммирование в целом - действительно тёмный лес, то по крайней мере у опушки мы кое-что знаем. Есть ряд стандартных задач, которые решаются макросами: например, сокращение бойлерплейта, автоматическое добавление «подчищающего» кода (например, with-open-file оборачивает тело в unwind-protect и гарантированно закрывает файл после выхода из формы), варианты управляющих конструкций, макросы для определений и т. п. В книге Грэма On Lisp рассмотрено много таких примеров.
> 1 <


Онлайн :

0 пользователь(ей), 8 гость(ей) :




Реклама на сайте: