Понимание того, как работает функция компиляции в Common Lisp SBCL

Я надеялся, что кто-нибудь сможет объяснить, почему функция 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

Почему вы вообще здесь используете compile? compile это что-то вроде eval: если ты думаешь, что тебе это нужно, подумай еще раз. Просто выполните (setf (symbol-function 'square) (lambda (x) (* x x))), чтобы привязать square к новой функции.

ad absurdum 26.06.2024 04:15

@adabsurdum Я согласен, хотя вам, очевидно, может понадобиться compile, если вы устанавливаете функции, которые вы динамически создаете во время выполнения, что немного похоже на то, что делает человек?

ignis volens 26.06.2024 10:07

@ignisvolens - это правда. Кажется, что SBCL compile немного подозрительно в этом отношении, но я не понимаю, почему OP не может просто сделать (setf (symbol-function 'square) (compile nil (lambda (x) (* x x)))), если им нужна явная компиляция. Я не думаю, что это не указано или недостаточно указано. К вашему сведению, CCL ведет себя так, как и ожидалось, в примерах OP.

ad absurdum 26.06.2024 20:00

Если все, что вам нужно, это то, что вы показываете в своем редактировании (т. е. вы не хотите динамически переопределять функции, вы можете рассмотреть define-функции, что упрощает задачу: (define-function foo (lambda (x) ...))

ignis volens 27.06.2024 11:46

Кажется, эта ошибка исправлена ​​в текущей основной ветке SBCL.

ignis volens 01.07.2024 12:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
129
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Компиляция уже скомпилированной функции в SBCL

Проблема очень тонкая:

(compile 'square (lambda (x) (* x x)))

COMPILE — это функция. Теперь посмотрите на (lambda (x) (* x x)). Что оценивается?

> (compiled-function-p (lambda (x) (* x x)))
T

Это скомпилированная функция!

В SBCL все функции по умолчанию компилируются.

Теперь мы вызываем COMPILE для уже скомпилированной функции.

Что происходит?

  • мы вызываем COMPILE с именем и скомпилированной функцией
  • SBCL не компилирует функцию, она уже скомпилирована
  • SBCL не устанавливает имя, поскольку не скомпилировал уже скомпилированную функцию.

Можно возразить, что функцию все же следует задать. Лучше обсудить это с сопровождающими

Компиляция исходного кода

Это работает:

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 создает скомпилированную функцию из определения. Если определение является лямбда-выражением, оно приводится к функции. Если определение уже является скомпилированной функцией, компиляция либо создает саму функцию (т. е. является идентификационной операцией), либо эквивалентную функцию».

ignis volens 26.06.2024 09:00

@ignisvolens Я бы согласился, но формулировка неудачна: «Если задано ненулевое имя, то результирующая скомпилированная функция заменяет существующее определение функции имени» -> что, если существующего определения нет?

Rainer Joswig 26.06.2024 09:06

хорошая точка зрения. Но я думаю, что это, скорее всего, недостаток стандарта.

ignis volens 26.06.2024 10:05

Я думаю, что это ошибка в 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

Rainer Joswig 26.06.2024 09:01

@RainerJoswig: спасибо, я не знал, что ты можешь это сделать. В таком случае это работает. Я обновлю свой ответ.

ignis volens 26.06.2024 09:04

Другие вопросы по теме