LINUX.ORG.RU

Рекурсивная реализация циклов lisp

 


0

2

Начал я учить лисп, пробую организовать циклы (велосипед, да) рекурсивно. Написал такой код:

(
	defun while (condition body)
	(		
      	(eval body)
      	(
      		if (eval condition) (while condition body)
      	)
	)
)

(defvar *ein* 10)
(
	while '( not ( = *ein* 0 ) )
	'( - *ein* 1 )
)
Лисп ругается:
Error in WHILE [or a callee]:
Error in TYPE-ERROR-DATUM [or a callee]: The slot CONDITIONS::DATUM is unbound in the object #<CONDITIONS::INTERNAL-TYPE-ERROR.0>.
P.S. У меня GNU common lisp 2.6.7

Чини скобки

Не затрагивая то, что ты фундаментально DOING IT WRONG, у тебя тело в defun обёрнуто в лишнюю пару скобок (эту можно легко увидеть, если расставить скобки по-нормальному, а не как у тебя).

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

theNamelessOne ★★★★★
()

Жуть какая... даже не знаю, с чего начать.

1. Отступы в лисп так не пишут. Скобка никогда не отрывается от относящегося к ней текста.

(defun while (condition body)
  ((eval body)
   (if (eval condition) (while condition body))))

(defvar *ein* 10)
(while '(not (= *ein* 0))
       '(- *ein* 1))

2. ((eval body) ...) писать нельзя. Первый элемент любого выражения должен быть либо именем функции, либо lambda-выражением. Исходя из контекста, предполагаю, что ты хотел выполнить (eval body) и (if ...) последовательно. Для этого есть форма progn. Должно быть

(defun while (condition body)
  (progn
    (eval body)
    (if (eval condition) (while condition body))))

или просто

(defun while (condition body)
  (eval body)
  (if (eval condition) (while condition body)))
defun — это тоже progn.

3. (while '(not (= *ein* 0)) '(- *ein* 1)) — это бесконечный цикл. Так как написано «пока не *ein* = 0, вычислить *ein* - 1». *ein* не меняется, значит никогда не станет равен нулю. Если ты хотел написать, «пока ... уменьшить *ein* на 1», то надо писать

(while '(not (= *ein* 0)) 
       '(setf *ein* (- *ein* 1)))
или
(while '(not (= *ein* 0)) 
       '(incf *ein* -1))

4. eval и списки как код используются очень редко. Правильным подходом было бы использовать аргументы-функции

(defun while (condition body)
  (funcall body)
  (if (funcall condition) (while condition body)))

(while (lambda () (not (= *ein* 0)))
       (lambda () (incf *ein* -1)))

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 2)
Ответ на: комментарий от theNamelessOne

Скорее уж макрос написать.

Рекурсивный? У меня не получилось:

(defmacro while (condition &body body)
  `(progn
    ,@body
    (if ,condition (while ,condition ,@body))))

при попытке выполнения выдаёт переполнение стека.

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

Рекурсивный?

Не, через do, например:

(defmacro while (condition &body body)
  `(do ()
       ((not ,condition))
     ,@body))
     
(let ((counter 10))
  (while (not (zerop counter))
    (format t "tick ~a...~%" counter)
    (decf counter))
  (format t "BOOM!~%"))

при попытке выполнения выдаёт переполнение стека

Потому что происходит бесконечное раскрытие рекурсивного макроса.

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 1)

возьми книгу любую по языку, который учишь. pcl, например. и не пиши на common lisp как будто пишешь на C

anonymous
()

и почитай, как работает eval

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

Не, через do, например

Если уже есть do, то зачем while?

А если без do, но с хвостовой рекурсией, то в голову приходит только что-то вроде

(defmacro while (condition &body body)
  `(labels ((loop ()
              (if ,condition (progn ,@body (loop)))))
     (loop)))

но это не очень наглядно

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

Так а зачем ты body в последнем вызове раскрываешь?

(defmacro while (condition body)
  `(progn ,body
          (when ,condition
            (while ,condition ,body))))


CL-USER> (defvar *var* 3)
*VAR*
CL-USER> (while (plusp *var*) (progn (format t "var: ~a~&" *var*) 
                                     (decf *var*)))
var: 3
var: 2
var: 1
NIL

CL-USER> (macroexpand-1 '(while (plusp *var*) (progn (format t "var: ~a~&" *var*) 
                                                     (decf *var*))))
(PROGN
 (PROGN (FORMAT T "var: ~a~&" *VAR*) (DECF *VAR*))
 (WHEN (PLUSP *VAR*)
   (WHILE (PLUSP *VAR*) (PROGN (FORMAT T "var: ~a~&" *VAR*) (DECF *VAR*)))))
T


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

У меня и твой код сработал. Как ты проверяешь то?

В clisp. Я так понимаю, он при вызове из REPL делает macroexpand-all.

monk ★★★★★
()
Ответ на: комментарий от monk
Compile-time error:
  Lock on package COMMON-LISP violated when binding LOOP as a local function
while in package COMMON-LISP-USER.
See also:
  The SBCL Manual, Node "Package Locks"
  The ANSI Standard, Section 11.1.2.1.2
   [Condition of type SB-INT:COMPILED-PROGRAM-ERROR]

Как-то не динамично со стороны sbcl получается.

В clisp да - такой шняги нету.

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

Другое дело

CL-USER> (locally (declare (disable-package-locks loop))
            (labels ((loop () (format nil "Hello world!~%")))
              (loop)))
"Hello world!
"
ados ★★★★★
()
Ответ на: комментарий от Oxdeadbeef
; compiling (DEFMACRO WHILE ...)

; file: /tmp/fileSW5zr9
; in: DEFMACRO WHILE
;     (DEFMACRO WHILE (CONDITION &BODY BODY)
;       `(PROGN
;         ,@BODY
;         (IF ,CONDITION
;             (WHILE ,CONDITION
;               ,@BODY))))
; --> PROGN EVAL-WHEN 
; ==>
;   (SB-C::%DEFMACRO 'WHILE
;                    (SB-INT:NAMED-LAMBDA (MACRO-FUNCTION WHILE)
;                        (#:EXPR #:ENV)
;                      (DECLARE (SB-C::LAMBDA-LIST (CONDITION &BODY BODY)))
;                      (DECLARE (IGNORE #:ENV))
;                      (SB-C::NAMED-DS-BIND (:MACRO WHILE . DEFMACRO)
;                          (CONDITION &BODY BODY)
;                          (CDR #:EXPR)
;                        (BLOCK WHILE `(PROGN ,@BODY #))))
;                    (SB-C:SOURCE-LOCATION))
; 
; caught STYLE-WARNING:
;   redefining COMMON-LISP-USER::WHILE in DEFMACRO
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition

CL-USER> 
loz ★★★★★
()
Ответ на: комментарий от Gentooshnik

А можешь опознать реализацию по сообщению об ошибке

В заголовке темы? Так реализация там написана: GNU common lisp 2.6.7

А ругается сначала на ошибку типа в while, а потом на то, что у этой ошибки нет описания (фактически, получилась внутренняя ошибка компилятора).

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