Я надеялся, что кто-нибудь сможет объяснить, почему функция compile
не работает так, как я ожидал.
Первый вопрос:
* (compile 'square (lambda (x) (* x x)))
SQUARE
NIL
NIL
Но потом:
* (square 3)
; in: SQUARE 3
; (SQUARE 3)
;
; caught STYLE-WARNING:
; undefined function: COMMON-LISP-USER::SQUARE
;
; compilation unit finished
; Undefined function:
; SQUARE
; caught 1 STYLE-WARNING condition
* (describe 'square)
COMMON-LISP-USER::SQUARE
[symbol]
SQUARE names an undefined function
Assumed type: FUNCTION
Что это значит?
Второй вопрос, начнём сначала:
* (defun square (x) (* x x x)) ;faulty definition
SQUARE
* (compile 'square (lambda (x) (* x x))) ;attempted correction
SQUARE
NIL
NIL
* (square 3)
27
Здесь старое определение все еще в силе. Однако в гиперспецификации говорится о compile
: «Если задано ненулевое имя, то результирующая скомпилированная функция заменяет существующее определение функции name, и имя возвращается в качестве основного значения;».
(ps: моя цель - определить (или обновить) функцию программно, но распознать ее статическим профилировщиком sbcl - в противном случае она игнорируется. Альтернативой является использование статистического профилировщика, но, похоже, это не помогает. работаю в windows-64.)
Редактировать 26.05.24 и 27.05.24: Спасибо за познавательные обсуждения. Я склонен согласиться с ignis volens, что реализацию compile
в sbcl можно улучшить. Возможно, кто-то с более глубоким пониманием деталей мог бы обсудить это с сопровождающими. Что касается моей первоначальной цели создания лямбда-выражений, которые распознаются статическим профилировщиком sbcl, у меня есть упрощенный пример, который, к лучшему или к худшему, похоже, работает и легко интегрируется в существующие структуры, поэтому я буду использовать его, если не будет недостатка:
* (defmacro init-fns (name1 name2)
`(progn (defun ,name1 ()) ;define dummy fns at top-level
(defun ,name2 ())))
INIT-FNS
* (init-fns square cube)
CUBE
* (defun build-fns ()
(setf (symbol-function 'square)
(compile nil '(lambda (x) (* x x))))
(setf (symbol-function 'cube)
(compile nil '(lambda (x) (* x x x)))))
BUILD-FNS
* (build-fns)
#<FUNCTION (LAMBDA (X)) {2479F75B}>
* (square 3)
9
* (cube 3)
27
@adabsurdum Я согласен, хотя вам, очевидно, может понадобиться compile
, если вы устанавливаете функции, которые вы динамически создаете во время выполнения, что немного похоже на то, что делает человек?
@ignisvolens - это правда. Кажется, что SBCL compile
немного подозрительно в этом отношении, но я не понимаю, почему OP не может просто сделать (setf (symbol-function 'square) (compile nil (lambda (x) (* x x))))
, если им нужна явная компиляция. Я не думаю, что это не указано или недостаточно указано. К вашему сведению, CCL ведет себя так, как и ожидалось, в примерах OP.
Если все, что вам нужно, это то, что вы показываете в своем редактировании (т. е. вы не хотите динамически переопределять функции, вы можете рассмотреть define-функции, что упрощает задачу: (define-function foo (lambda (x) ...))
Кажется, эта ошибка исправлена в текущей основной ветке SBCL.
Компиляция уже скомпилированной функции в SBCL
Проблема очень тонкая:
(compile 'square (lambda (x) (* x x)))
COMPILE
— это функция. Теперь посмотрите на (lambda (x) (* x x))
. Что оценивается?
> (compiled-function-p (lambda (x) (* x x)))
T
Это скомпилированная функция!
В SBCL все функции по умолчанию компилируются.
Теперь мы вызываем COMPILE
для уже скомпилированной функции.
Что происходит?
COMPILE
с именем и скомпилированной функциейМожно возразить, что функцию все же следует задать. Лучше обсудить это с сопровождающими
Компиляция исходного кода
Это работает:
CL-USER> (compile 'square '(lambda (x) (* x x)))
SQUARE
NIL
NIL
CL-USER> #'square
#<FUNCTION SQUARE>
Это работает, потому что '(lambda (x) (* x x))
возвращает список, который является допустимой LAMBDA
макроформой. Компилятор успешно скомпилирует этот исходный код, а затем присвоит полученной скомпилированной функции символьную функцию SQUARE
.
Сравните с другой реализацией CL, здесь LispWorks.
Сравните с LispWorks, который по умолчанию не компилирует функции.
CL-USER 134 > (let ((f (compile nil '(lambda (x) (* x x)))))
(compile 'square f))
;;;*** Warning in SQUARE: The definition supplied for SQUARE is already compiled.
SQUARE
((SQUARE #<CONDITIONS::SIMPLE-STYLE-WARNING 80101F2C9B>))
NIL
CL-USER 135 > (symbol-function 'square)
#<Function 14 8020000CB9>
LispWorks предупреждает, что функция уже скомпилирована, и все равно устанавливает символьную функцию SQUARE
.
Требуется установить функцию: 'compile создает скомпилированную функцию из определения. Если определение является лямбда-выражением, оно приводится к функции. Если определение уже является скомпилированной функцией, компиляция либо создает саму функцию (т. е. является идентификационной операцией), либо эквивалентную функцию».
@ignisvolens Я бы согласился, но формулировка неудачна: «Если задано ненулевое имя, то результирующая скомпилированная функция заменяет существующее определение функции имени» -> что, если существующего определения нет?
хорошая точка зрения. Но я думаю, что это, скорее всего, недостаток стандарта.
Я думаю, что это ошибка в SBCL. В частности, кажется, что если вы передадите compile
скомпилированную функцию в качестве второго аргумента, все будет работать неправильно.
Это работает так, как и следовало ожидать (каждый пример выполняется в холодном SBCL после версии 2.4.5):
> (fboundp 'square)
nil
> (compile 'square '(lambda (x) (* x x)))
square
nil
nil
> (fboundp 'square)
#<function square>
> (square 10)
100
Но
> (fboundp 'square)
nil
> (compile 'square (lambda (x) (* x x)))
square
nil
nil
> (fboundp 'square)
nil
> (type-of (lambda (x) (* x x)))
compiled-function
Эквивалентные вещи применяются в вашем случае переопределения.
SBCL обычно не имеет интерпретируемых функций, но стандарт подготовлен для этого случая:
compile создает скомпилированную функцию из определения. Если определение является лямбда-выражением, оно приводится к функции. Если определение уже является скомпилированной функцией, компиляция либо создает саму эту функцию (т. е. является идентификационной операцией), либо эквивалентную функцию.
Если имя равно nil, результирующая скомпилированная функция возвращается непосредственно как основное значение. Если задано ненулевое имя, то результирующая скомпилированная функция заменяет существующее определение функции name, и имя возвращается в качестве основного значения; если имя является символом, который называет макрос, его функция макроса обновляется, и имя возвращается в качестве основного значения.
Из КОМПИЛЯ, мой акцент.
Таким образом, SBCL неправильно рассматривает случай, когда определение (a) еще не является определением, связанным с name
, а (b) уже является скомпилированной функцией. Я бы сообщил об этом как об ошибке.
Это работает правильно, если определение является интерпретируемой функцией. Вы можете заставить это произойти, установив для *evaluator-mode* значение :interpret
:
> (setf *evaluator-mode* :interpret)
:interpret
> (compile 'square (lambda (x) (* x x)))
square
nil
nil
> (fboundp 'square)
#<function (lambda (x) nil) {7007117D6B}>
> (square 100)
10000
> (type-of #'square)
compiled-function
> (type-of (lambda (x) (* x x)))
sb-kernel:interpreted-function
Похоже, ошибка связана только с скомпилированной функцией.
На практике, если вы хотите установить фрагмент исходного кода в форме лямбда-выражения, то в этом случае все будет в порядке, поскольку SBCL правильно обрабатывает этот случай, как вы можете видеть выше. или вы можете написать что-то вроде этого:
(defun compile-and-install (name definition)
;; Does not handle the macro case that COMPILE does
(setf (fdefinition name)
(typecase definition
(compiled-function definition)
(t (compile nil definition))))
name)
Ради интереса можно было бы попробовать это с интерпретируемой функцией в SBCL. sbcl.org/manual/index.html#Interpreter
@RainerJoswig: спасибо, я не знал, что ты можешь это сделать. В таком случае это работает. Я обновлю свой ответ.
Почему вы вообще здесь используете
compile
?compile
это что-то вродеeval
: если ты думаешь, что тебе это нужно, подумай еще раз. Просто выполните(setf (symbol-function 'square) (lambda (x) (* x x)))
, чтобы привязатьsquare
к новой функции.