#macros #scheme #lisp #common-lisp #lisp-macros
#макросы #схема #lisp #common-lisp #lisp-макросы
Вопрос:
У меня проблема с макросами в моем интерпретаторе lisp, написанном на JavaScript. проблема в этом коде:
(define log (. console "log"))
(define (alist->object alist)
"(alist->object alist)
Function convert alist pairs to JavaScript object."
(if (pair? alist)
((. alist "toObject"))))
(define (klist->alist klist)
"(klist->alist klist)
Function convert klist in form (:foo 10 :bar 20) into alist
in form ((foo . 10) (bar . 20))."
(let iter ((klist klist) (result '()))
(if (null? klist)
result
(if (and (pair? klist) (pair? (cdr klist)) (key? (car klist)))
(begin
(log ":::" (cadr klist))
(log "data" (. (cadr klist) "data"))
(iter (cddr klist) (cons (cons (key->string (car klist)) (cadr klist)) result)))))))
(define (make-empty-object)
(alist->object '()))
(define empty-object (make-empty-object))
(define klist->object (pipe klist->alist alist->object))
;; main function that give problems
(define (make-tags expr)
(log "make-tags" expr)
`(h ,(key->string (car expr))
,(klist->object (cadr expr))
,(if (not (null? (cddr expr)))
(if (and (pair? (caddr expr)) (let ((s (caaddr expr))) (and (symbol? s) (eq? s 'list))))
`(list->array (list ,@(map make-tags (cdaddr expr))))
(caddr expr)))))
(define-macro (with-tags expr)
(make-tags expr))
Я вызываю этот макрос, используя этот код:
(define (view state actions)
(with-tags (:div ()
(list (:h1 () (value (cdr (assoc 'count (. state "counter")))))
(:button (:onclick (lambda () (--> actions (down 1)))) "-")
(:button (:onclick (lambda () (--> actions (up 1)))) " ")))))
который должен расширяться почти до того же кода:
(define (view state actions)
(h "div" (make-empty-object)
(list->array (list
(h "h1" (make-empty-object) (value (cdr (assoc 'count (. state "counter")))))
(h "button" (klist->object `(:onclick ,(lambda () (--> actions (down 1))))) "-")
(h "button" (klist->object `(:onclick ,(lambda () (--> actions (up 1))))) " ")))))
Эта функция работает. У меня проблема с расширенным кодом, использующим мой макрос, который вызывает основную функцию, не знаю, как LIPS должен вести себя, когда он находит:
(:onclick (lambda () (--> actions (down 1))))
внутри кода, и вы пытаетесь обработать его следующим образом:
,(klist->object (cadr expr))
Прямо сейчас мой lisp работает так, что лямбда-код помечается как данные (для флага data установлено значение true, это хак для предотвращения рекурсивного вычисления некоторого кода из макросов), а klist->object
функция получает лямбда-код в виде списка, а не функции.
Как это должно работать в Scheme или Common Lisp? Должен klist->object
ли быть получен объект функции (вычисляется лямбда) или структура списка с лямбдой в качестве первого символа? Если второе, то как я мог бы написать свою функцию и макрос для оценки лямбда, должен ли я использовать eval (для меня это своего рода хак).
Извините, не знаю, как это проверить, с более свободным от ошибок LISP.
Редактировать:
Я попытался применить подсказку от @jkiiski в guile (потому что в моем lisp это не работало)
;; -*- sheme -*-
(define nil '())
(define (key? symbol)
"(key? symbol)
Function check if symbol is key symbol, have colon as first character."
(and (symbol? symbol) (eq? ":" (substring (symbol->string symbol) 0 1))))
(define (key->string symbol)
"(key->string symbol)
If symbol is key it convert that to string - remove colon."
(if (key? symbol)
(substring (symbol->string symbol) 1)))
(define (pair-map fn seq-list)
"(seq-map fn list)
Function call fn argument for pairs in a list and return combined list with
values returned from function fn. It work like the map but take two items from list"
(let iter ((seq-list seq-list) (result '()))
(if (null? seq-list)
result
(if (and (pair? seq-list) (pair? (cdr seq-list)))
(let* ((first (car seq-list))
(second (cadr seq-list))
(value (fn first second)))
(if (null? value)
(iter (cddr seq-list) result)
(iter (cddr seq-list) (cons value result))))))))
(define (klist->alist klist)
"(klist->alist klist)
Function convert klist in form (:foo 10 :bar 20) into alist
in form ((foo . 10) (bar . 20))."
(pair-map (lambda (first second)
(if (key? first)
(cons (key->string first) second))) klist))
(define (h props . rest)
(display props)
(display rest)
(cons (cons 'props props) (cons (cons 'rest rest) nil)))
(define (make-tags expr)
`(h ,(key->string (car expr))
(klist->alist (list ,@(cadr expr)))
,(if (not (null? (cddr expr)))
(if (and (pair? (caddr expr)) (let ((s (caaddr expr))) (and (symbol? s) (eq? s 'list))))
`(list->array (list ,@(map make-tags (cdaddr expr))))
(caddr expr)))))
(define-macro (with-tags expr)
(make-tags expr))
(define state '((count . 10)))
(define xxx (with-tags (:div ()
(list (:h1 () (cdr (assoc 'count state)))
(:button (:onclick (lambda () (display "down"))) "-")
(:button (:onclick (lambda () (display "up"))) " ")))))
но получил ошибку:
ОШИБКА: несвязанная переменная: :onclick
Я нашел решение для своего lisp, вот код:
(define (pair-map fn seq-list)
"(seq-map fn list)
Function call fn argument for pairs in a list and return combined list with
values returned from function fn. It work like the map but take two items from list"
(let iter ((seq-list seq-list) (result '()))
(if (null? seq-list)
result
(if (and (pair? seq-list) (pair? (cdr seq-list)))
(let* ((first (car seq-list))
(second (cadr seq-list))
(value (fn first second)))
(if (null? value)
(iter (cddr seq-list) result)
(iter (cddr seq-list) (cons value result))))))))
(define (make-tags expr)
(log "make-tags" expr)
`(h ,(key->string (car expr))
(alist->object (quasiquote
;; create alist with unquote for values and keys as strings
,@(pair-map (lambda (car cdr)
(cons (cons (key->string car) (list 'unquote cdr))))
(cadr expr))))
,(if (not (null? (cddr expr)))
(if (and (pair? (caddr expr)) (let ((s (caaddr expr))) (and (symbol? s) (eq? s 'list))))
`(list->array (list ,@(map make-tags (cdaddr expr))))
(caddr expr)))))
Итак, в моем коде я пишу какой-то мета-макрос, я пишу quasiquote как список, который будет оцениваться так же, как если бы я использовал в своем исходном коде:
(klist->object `(:onclick ,(lambda () (--> actions (down 1)))))
Я использую alist->object
и новую функцию pair-map
, поэтому я могу отменить кавычки значения и преобразовать символ ключа в строку.
так ли это должно быть реализовано в scheme? не уверен, нужно ли мне исправлять мой lisp или макросы там работают правильно.
Комментарии:
1. Вы используете запятую для вычисления лямбда-выражения, поэтому оно должно быть объектом функции, как если бы вы написали
(list :onclick (lambda ...))
(цитируя:onclick
if, если оно не самооценка в вашем lisp).2. @jkiiski в моем макросе, который не использует запятую для лямбда (
,(lambda ...)
, это в исходном коде, который я хочу попытаться переписать как макрос,,(klist->object (cadr expr))
и он получает лямбду как структуру списка.3. Итак, проблема в том, что макрос в настоящее время не расширяется до приведенного вами примера расширения? Глядя на
make-tags
-function, кажется, что вы вызываетеklist->object
во время макрорасширения, а не возвращаете его. Если я вас правильно понимаю, вы бы хотели, чтобы это было что-то вроде´(h ... (klist->object (list ,@(cadr expr))) ...)
(предполагается, что это обратная ссылка в начале, но я не знаю, как написать ее в комментарии здесь).4. @jkiiski попытался применить ваше решение к схеме guile, потому что в моем коде оно не работало, нашел исправление для моего lisp, но хочу также знать, как это должно быть реализовано в scheme. (код для хитрости в свернутом фрагменте).
5. Ключевые слова в Guile записываются
#:onclick
, а не:onclick
(я думаю, что есть способ заставить его принять и последнее, но я не настолько знаком с Guile / Scheme), поэтому он рассматривается:onclick
как переменная, а не ключевое слово для самооценки.