LINUX.ORG.RU

Common Lisp: eval-when

 ,


2

1

Мужики, поясните за eval-when. В целом, эмпирически, я представляю как работает eval-when в разрезе compile-file, (load .lisp) и (load .fasl), но таблички в HyperSpec-е и CLtL2 вообще никак соотнести со своими представлениями не могу. В частности, не могу понять что из-себя compile-time-too и non-compile-time режимы представляют и когда они наступают. В описании какие-то мутные, если можно так сказать, определения.

Для удобства, приведу таблички здесь: http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm

| CT  | LT  | E   | Mode | Action   | New Mode         |
|-----+-----+-----+------+----------+------------------|
| Yes | Yes | -   | -    | Process  | compile-time-too |
| No  | Yes | Yes | CTT  | Process  | compile-time-too |
| No  | Yes | Yes | NCT  | Process  | non-compile-time |
| No  | Yes | No  | -    | Process  | non-compile-time |
| Yes | No  | -   | -    | Evaluate | -                |
| No  | No  | Yes | CTT  | Evaluate | -                |
| No  | No  | Yes | NCT  | Discard  | -                |
| No  | No  | No  | -    | Discard  | -                |

https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node68.html#SECTION00933000000...

| LT  | CT  | EX  | CTTM | Action                                |
|-----+-----+-----+------+---------------------------------------|
| yes | yes | -   | -    | process body in compile-time-too mode |
| yes | no  | yes | yes  | process body in compile-time-too mode |
| yes | no  | -   | no   | process body in non-compile-time mode |
| yes | no  | no  | -    | process body in non-compile-time mode |
| no  | yes | -   | -    | evaluate body                         |
| no  | no  | yes | yes  | evaluate body                         |
| no  | no  | -   | no   | do nothing                            |
| no  | no  | no  | -    | do nothing                            |

Статью (на русском) Fare про eval-when знаю, но считаю её ещё более запутанной, чем описания в первоисточниках.

P.S. У меня вот такие таблички получились:

| :compile-toplevel | :load-toplevel | compile-file   | load .fasl |
|-------------------+----------------+----------------+------------|
| -                 | -              | -              | -          |
| +                 | -              | eval           | -          |
| -                 | +              | compile        | eval       |
| +                 | +              | eval & compile | eval       |

| :execute | load .lisp     |
|----------+----------------|
| -        | -              |
| +        | eval           |
Это всё для случая, когда eval-when на верхнем уровне. В подформах для eval-when имеет смысл только :execute. Если стоит — обрабатываем, если нет — nil.

Причём под eval понимается настоящий eval (т.е. вместе с раскрытием макросов и implicit compilation). Фактически, при установленных :compile-toplevel и :load-toplevel макровызовы в подформах будут раскрывается компилятором дважды. Один раз при eval и второй раз при явной компиляции. А вот читаться (read) форма будет один раз. По крайней мере так в SBCL и Clozure CL. В любом случае побочных эффектов при раскрытии макросов лучше избегать, ну или делать их предсказуемыми (чтобы макровызов с одними и теми же аргументами давал один и тот же результат [за исключением, конечно, gensym-ов]).

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

Да понятно, что eval-when в подавляющем большинстве случаев нужен только в двух комбинациях: (:load-toplevel :execute) и (:compile-toplevel :load-toplevel :execute). Причём первый случай уже неявно присутствует для всех toplevel форм. Просто у меня эти таблички вызывают какой-то внутренний дискомфорт и ощущение, что я чего-то не понимаю :) Вот и задал вопрос, может всё на самом деле сложнее, чем я себе представляю.

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

Тоже когда-то ставило в тупик. non-compile-time и compile-time-too термины spec-a (тех кто писал spec, а это Guy Steele и прочие мозг^H^H^H^H академики придумавшие Scheme) и не очень удачные IMHO. Чтобы врубиться надо вспомнить, что это относится к eval-when, то есть это режимы *EVAL*. То есть, не-вычисляю-в-compile-time и вычисляю-в-том-числе-и-в-compile-time. Глубинного смысла не ищи, в таблички не вчитывайся, обычно это используется в интуитивном смысле и для top-level.

seg-fault ()
Ответ на: комментарий от deadlock

В частности, не могу понять что из-себя compile-time-too и non-compile-time режимы представляют и когда они наступают

compile-time-too = «выполнять и при компиляции». Наступает внутри (:compile :load) или (:compile :load :execute). Собственно, единственный режим (по Fare), который имеет смысл использовать.

non-compile-time = «не выполнять при компиляции». Наступает внутри (:load) или (:load :execute). Если верить Fare, является режимом по-умолчанию.

Внутри этих режимов по-разному трактуется (eval-when (:execute) ...). Для compile-time-too он выполнится при компиляции, для non-compile-time выкинется, для не top-level превратится в progn.

Смысл в том, что бывают макросы, которые должны по-разному раскрываться при компиляции и выполнении. В http://www.lispworks.com/documentation/HyperSpec/Body/s_eval_w.htm есть пример

(defun foo (x) (+ x 3)) =>

(progn (eval-when (:compile-toplevel) 
          (compiler::notice-function-definition 'foo '(x)))
       (eval-when (eval-when (:execute :load-toplevel)
          (setf (symbol-function 'foo) #'(lambda () (+ x 3)))))

Здесь при компиляции будет выполнена регистрация функции, а при выполнении установка значения (symbol-function 'foo).

Если хочешь, чтобы не выполнялось на top-level, можно завернуть в (:execute). Ещё из неожиданных (интуитивно) комбинаций: (eval-when (:compile-toplevel) (eval-when (:compile-toplevel) ...)) всегда nil, так как первый слой :compile-toplevel переводит режим в eval, а второй для eval не выполняется.

Всё это было актуально до появления SLIME + asdf, где теперь требуют, чтобы eval и компиляция давали один и тот же результат.

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

Благодарю, monk! Всё верно ты сказал. Я тут тоже подразобрался. Таблички в CLtL2 и HyperSpec-е абсолютно адекватны, мои же — херота. Для полноты картины можно добавить то, что eval-when-ами можно прыгать из режима в режим (NCT, CTT) до тех пор пока eval-when не окажется non-toplevel формой. Всё начинается с NCT.

(eval-when (:load-toplevel :compile-toplevel) ; NCT->CCT
  (eval-when (:load-toplevel)                 ; CTT->NCT
    (progn
      (eval-when (:load-toplevel :compile-toplevel) ; NCT->CTT
        (eval-when (:execute)
          (format t "~&compile time eval~%"))
        (format t "~&compile time eval & load time~&")))))

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

Ещё из неожиданных (интуитивно) комбинаций: (eval-when (:compile-toplevel) (eval-when (:compile-toplevel) ...)) всегда nil, так как первый слой :compile-toplevel переводит режим в eval, а второй для eval не выполняется.

Да, верно. Верхний eval-when вне зависимости от режима (NCT или CTT) говорит о том, что нужно выполнить тело в compile time, а нижний eval-when, на сколько я понял, теперь считается за non-toplevel form — :compile-toplevel игнорируется, :execute не стоит, посему nil.

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

Ну, дебил, наверное не дебил. Но статья его, по мне так, больше запутывает. Мало того, что очень сумбурно написана, да еще и обманывает местами. Вообщем, вреда больше чем пользы. Лучше спеку вкурить.

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

Напрмер, Fare пишет:

если файл загружается с помощью load, то каждая форма проходит через стадии: чтение, раскрытие макросов, компиляция, загрузка;

Тут совершенно не понятно, что меется ввиду (load .lisp) или (load .fasl). Но это даже не важно, потому что ни в том и ни в другом случает это не правда. В случае с (load .lisp) каждая форма верхнего уровня eval-лится, или не eval-ится, если на верхнем уровне eval-when без :execute. В случае с (load .fasl) опять же не правда, никакие макросы не раскрываются и ничего не компилируется — всё уже раскрыто и скомпилировано.

если формы набираются в REPL, то форма проходит все стадии от чтения до исполнения.

Это тоже не правда. Опять же формы в repl-е eval-ятся. Никакой явной компиляции не происходит: impicit compilation eval-а не считается - это особенности реализации, implicit compilation может и не быть. Стоит заметить, что Fare хочет отобразить свои «стадии» на ситуации в eval-when. В случае с (load .lisp) режимы CTT, NCT не имеют никакого смысла, всё зависит от того, как реализован eval.

Вторая стадия обработки кода (сразу после чтения формы) - раскрытие макросов. То, как проходит раскрытие макросов, определяется макросами, определенными через DEFMACRO, DEFINE-SYMBOL-MACRO и их лексическими вариантами MACROLET, SYMBOL-MACROLET, а также макросами, определенными с помощью DEFINE-SETF-EXPANDER и DEFINE-MODIFY-MACRO, макросами компиляции DEFINE-COMPILER-MACRO и динамической переменной *MACROEXPAND-HOOK*. [...] Затем идут следующие стадии обработки: либо компиляция, после которой следует или не следует загрузка, или же непосредственное исполнение без компиляции.

И снова обман, если форма обрабатываются в CTT (:load :compile), то происходит так: читается форма, eval-ится компилятором (возможно, implicit compilation) в образе в котором он запущен, компилируется с переносом в fasl. Таким образом макросы раскрываются дважды. По крайней мере, так в SBCL и Clozure CL. В NCT (:load) всё тоже самое, но eval-а не происходит.

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

То есть для того что бы понять, как обрабатывается код в CL и какова семантика eval-when в частности, нужно сделать 2 вещи: прочитать внимательно спеку, не читать чудную статью Fare.

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

не понятно, что имеется ввиду (load .lisp) или (load .fasl)

(load .lisp). fasl есть не везде.

В случае с (load .lisp) каждая форма верхнего уровня eval-лится

Так это и есть «стадии: чтение, раскрытие макросов, компиляция, загрузка»

Можешь проверить: внутри eval работают макросы чтения, макросы, макросы компиляции, load-time-value.

Никакой явной компиляции не происходит: impicit compilation eval-а не считается - это особенности реализации

Бывают даже интерпретаторы CL. Но в них всё равно есть compile-file :-). «Компиляция» в данном контексте — преобразование в вычислимую форму. Для интерпретатора, как правило, список. Для SBCL — машинный код. http://www.lispworks.com/documentation/HyperSpec/Body/03_bbb.htm

Вторая стадия обработки кода (сразу после чтения формы) - раскрытие макросов.... И снова обман, если форма обрабатываются в CTT (:load :compile)

Чтобы перейти в CTT необходимо начать стадию компиляции. Макрос в toplevel может читаться (и раскрываться до начала компиляции). Более того, технически так и происходит, так как (eval-when ...) обычно находится в том, во что раскрываются defun, defclass, defmacro, ...

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

если файл загружается с помощью load, то каждая форма проходит через стадии: чтение, раскрытие макросов, компиляция, загрузка

Кстати, такого он вообще не пишет. В оригинале «However, depending on whether you're typing code at the REPL, LOADing a Lisp source file, COMPILE-FILEing it then LOADing a compiled file, EVALing or COMPILEing a form, then which of compile-time, load-time and execution-time happens, which is deferred, which doesn't happen, and how they interleave with each other and with macro-expansion-time may vary wildly»

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

Так это и есть «стадии: чтение, раскрытие макросов, компиляция, загрузка»

То что макросы раскрываются и неявная компиляция (в большинстве реализаций) происходит с последующим выполнением в этом сомнений нет. Иначе бы eval не работал. А вот что такое «загрузка» в контексте eval, понятие не имею. Я о том, что eval-when не контролирует аспекты работы eval. А Fare говорит об этим стадиях как раз в разрезе eval-when. А в действительности eval-when имеет смысл только для компилятора (compile-file). Фактически, сам eval смотрит в eval-when исключительно на :execute.

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

Можешь проверить: внутри eval работают макросы чтения, макросы, макросы компиляции, load-time-value.

Ясно, понятно. Что тут проверять-то.

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

Бывают даже интерпретаторы CL. Но в них всё равно есть compile-file :-). «Компиляция» в данном контексте — преобразование в вычислимую форму. Для интерпретатора, как правило, список. Для SBCL — машинный код. http://www.lispworks.com/documentation/HyperSpec/Body/03_bbb.htm

Поэтому я и говорю, что неявная компиляция в eval — особенность реализации. SBCL можно перевести eval в интерпретатор http://www.sbcl.org/manual/index.html#Interpreter.

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

Чтобы перейти в CTT необходимо начать стадию компиляции.

Это смотря что считать за компиляцию.

Макрос в toplevel может читаться (и раскрываться до начала компиляции).

Естественно. http://www.lispworks.com/documentation/HyperSpec/Body/03_bca.htm совершенно чётко сказано как CL будет обрабатывать toplevel формы. Но самое интересное начинается, когда все результаты макроформ на верхнем уровне, тела progn, locally и eval-when подняты в toplevel. Если у нас CTT, то сначала eval в текущем образе вызова compile-file, а потом явная компиляция с переносом в fasl (я уже повторяюсь). Это очень важный момент, ибо в CTT получается макросы будут раскрыты дважды. Вот в CLtl2 чётко сказано что будет происходить в CTT (причём даже порядок задан):

If the form is not an eval-when form, then do two things. First, if the current processing mode is compile-time-too mode, evaluate the form in the compiler's executing environment. Second, perform normal compiler processing of the form (compiling functions defined by defun forms, and so on).

Обо всё этом Fare не рассказывает, у него получается какая-то грубая модель. В действительности же, без введения эти режимов (CTT, NCT) не возможно понять семантику eval-when совершенно чётко.

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

Это смотря что считать за компиляцию.

http://www.lispworks.com/documentation/HyperSpec/Body/03_bbb.htm

Если у нас CTT, то сначала eval в текущем образе вызова compile-file, а потом явная компиляция с переносом в fasl

Про это он пишет: «Then the processor goes through some stages of evaluation: either compilation that may or may not be followed by loading, or direct execution that may or may not involve some kind of compilation. Whichever stages happen then may or may not be themselves interleaved with the macro-expansion of the given form itself: it is allowed for the implementation to start compiling or executing part of a form while the rest of it isn't macroexpanded, or to fully macroexpand before it compiles»

Обо всё этом Fare не рассказывает, у него получается какая-то грубая модель.

У него цель не рассказать нюансы, а найти какие варианты eval-when работают одинаково в compile-file, ASDF, SLIME (так как эти варианты с точки зрения современного Common Lisp должны быть семантически идентичными). И он приходит к очевидному выводу, что eval-when нужен только в варианте eval-always

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

Кстати, да, верное замечание. Переведено через жопу местами. Но тем не менее даже в оригинальной фразе всё в кучу замешано. На самом деле корректность этой фразы сложно подтвердить или опровергнуть, она какая-то гуманитарно-библейская. Но так нельзя всё в кучу мешать это абсолютно точно.

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

Но так нельзя всё в кучу мешать это абсолютно точно.

Смотря какая цель. Если цель — обосновать, почему eval-when плохо и «вредно для вашего психического здоровья», то так и нужно писать. Чтобы читатель ужаснулся. Вот здесь Линус Торвальдс описывает, чем плох C++ ещё более безумными выражениями.

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

Но Фаре врет, а Линус правду пишет.

Ты считаешь, что программисту (не разработчику компилятора CL) следует использовать (eval-when ...) в какой-то комбинации кроме (eval-when (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE) ...) ?

И уверен, что C++ является ужасным языком и не годится для разработки программного обеспечения?

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

Если цель — обосновать, почему eval-when плохо и «вредно для вашего психического здоровья», то так и нужно писать.

Хм. Так почему же? Я ответа не нашёл... Ну если полагать что CL обрабатывает код так как написал Fare и семантика у eval-when именно такая, то конечно очень вредно. Но действительность не такая, CLtL2 и clhs достаточно строго все эти процессы определяет. Что хотел сказать Fare этим библейским описанием не ясно. В конце он делает изумительный вывод:

Если вам нравится использовать нетривиальное метапрограммирование, то рекомендую избегать такого примитивного в этом отношении языка, как Common Lisp, и использовать современный язык с многостадийной компиляцией и четко определенной семантикой (например, язык модулей PLT Scheme, camlp4 для OCaml, и другие системы, обрабатывающие файлы детерминированным образом). У макросов PLT есть ряд преимуществ: гигеничность, совместимость с отладчиками и другими инструментами и т.п.

Я не не очень понимаю, а разве в CL не многостадийная компиляция, разве процесс не имеет чётко определённую семантику? Всё это есть и всё строго определено. Что подразумевается под детерминированностью я тоже не очень понял. То что в CL можно в compile time использовать всю функциональность, что и в run time — это благо, гибкость. Конечно за такие возможности нужно платить возможностью прострелить ногу. Ну тут как бы бесплатного в мире ничего не бывает.

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

Что подразумевается под детерминированностью я тоже не очень понял.

То, что в PLT Scheme, например, Компиляция (при необходимости) + загрузка модуля — операция атомарная, причём окружение компиляции строго задано. Поэтому любые изменения в коде не требуют перезапуска среды.

А в SBCL я могу один и тот же пакет загрузить через quicklisp (+asdf) — будет compile-file при первой загрузке и загрузка скомпилированного при последующих. Могу чуть поправить, снова загрузить (будет другое окружение компиляции, если в загруженном было defconstant, получу ошибку). Могу поправить в SLIME, загрузить (загрузится через load — eval-when отработает по-другому).

То что в CL можно в compile time использовать всю функциональность, что и в run time — это благо, гибкость. Конечно за такие возможности нужно платить возможностью прострелить ногу. Ну тут как бы бесплатного в мире ничего не бывает.

В PLT Scheme (нынче он называется Racket) тоже в compile time можно использовать всю функциональность. Но вероятность случайно выстрелить в ногу (при разработке в SLIME всё работает, а при загрузке в чистую платформу выясняется, что eval-when забыли) гораздо меньше.

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

Но действительность не такая, CLtL2 и clhs достаточно строго все эти процессы определяет.

Если так, то напиши, какую комбинацию флагов eval-when, кроме «всё включено» ты хотел бы использовать в своей программе и какой цели этим добьёшься?

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

Все комбинации нужны и полезны.

Да ну?

(eval-when () ...) точно не нужна, так как = (when nil ...)

По остальным можешь привести разумный use-case?

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

Если так, то напиши, какую комбинацию флагов eval-when, кроме «всё включено» ты хотел бы использовать в своей программе и какой цели этим добьёшься?

Ну, во-первых, это так (всё строго определено) вне зависимости от «нужности» тех или иных комбинаций, а во-вторых, ну давай рассмотрим:

  • (:load-toplevel :execute) — NCT mode (by default). CHECK.
  • (:load-toplevel :compile-time :execute) — CTT mode. CHECK.
  • (:compile-time :execute) — eval в compile time, редкий кейс, но вполне уместен для реализации механики по аналогии defun или defmacro (сам пример приводил), т.е. когда надо что-нибудь прочкать в CT и оповестить (например о redefine), но вместе с тем заведомо зная что во время загрузки .fasl-а это точно не будет нужно. CHECK.

Вообщем-то это всё. Все комбинации eval-when. Только :execute нужно везде на верхнем уровне ставить, иначе (load .lisp) поломается. Никаких других «бессмысленных», по словам Fare, комбинаций просто нет.

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

То, что в PLT Scheme, например, Компиляция (при необходимости) + загрузка модуля — операция атомарная

Что значит атомарная? Эти вещи происходят обычно в разные моменты времени.

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

причём окружение компиляции строго задано

Что значит «строго»? В CL тоже задано строго, строго в контексте текущего образа. А как в racket?

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

Но вероятность случайно выстрелить в ногу (при разработке в SLIME всё работает, а при загрузке в чистую платформу выясняется, что eval-when забыли) гораздо меньше.

Хм. А что именно в racket уменьшает вероятность? Ну конечно, в CL когда мы что-то разрабатываем, много чего запускаем в slime, полный хаос в образе. Но ведь никто в здравом уме не будет с текущего расфаршированного образа компилировать что-то и потом надеяться на то, что этот fasl будет вменяемым?

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

(:load-toplevel :execute) — NCT mode (by default). CHECK.

Сам пишешь, что это и так by default. Зачем писать eval-when, если он ничего не делает?

(:load-toplevel :compile-time :execute)

Это тот самый «всё включено».

(:compile-time :execute)

Fare этот вариант упоминает. И указывает на полный бардак, который такая комбинация вносит при использовании SLIME и ASDF. Использовать можно, но очень осторожно (например для readtable внутри файла).

Вообщем-то это всё. Все комбинации eval-when.

Итого, ты соглашаешься с Fare.

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

Что значит «строго»? В CL тоже задано строго, строго в контексте текущего образа. А как в racket?

В CL аналог поведения Racket можно получить, если перед компиляцией каждого пакета перезапускать образ и запускать asdf:load-op.

То есть Racket гарантирует, что в образе компиляции доступны все модули, от которых зависит текущий модуль (также как asdf грузит зависимости) и также гарантирует, что компиляция модуля никак не зависит от того, компилировались ли в этот раз другие модули или просто загружались.

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

Хм. А что именно в racket уменьшает вероятность?

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

Ну конечно, в CL когда мы что-то разрабатываем, много чего запускаем в slime, полный хаос в образе. Но ведь никто в здравом уме не будет с текущего расфаршированного образа компилировать что-то и потом надеяться на то, что этот fasl будет вменяемым?

Отладку делают именно в этом хаосе. И когда в «текущем расфаршированном» образе всё работает как надо, то мало кто повторяет все тесты для компиляции с нуля.

Более того, если пакетов в программе больше одного (даже больше одного файла), то может получится так, что компиляция с нуля всех пакетов работает. Но при изменении одного файла и инкрементальной компиляции через quicklisp/asdf .fasl ломается. Единственный метод починки этой ситуации, который я знаю: rm *.fasl и перекомпилировать всю систему, так как у asdf даже нету команды «перекомпилировать всё, что зависит от этого пакета».

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

Fare этот вариант упоминает. И указывает на полный бардак, который такая комбинация вносит при использовании SLIME и ASDF. Использовать можно, но очень осторожно (например для readtable внутри файла).

Ну конечно надо быть осторожным, надо вообще быть осторожным во всём. И при чём тут вообще slime и asdf? defun ведь ничего не ломает. Бардак — это не проблема eval-а в ct и не следствие. Бардак — это когда ты что-то не правильно используешь, ввиду не понимая как это работает. Статья Fare ничего не даёт для понимания. Он просто батхёртит про недетерминированность и сложность.

Итого, ты соглашаешься с Fare.

Fare говорит про какие-то другие «бессмысленные» комбинации, а их просто нет.

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

В CL аналог поведения Racket можно получить, если перед компиляцией каждого пакета перезапускать образ и запускать asdf:load-op.

Ну да. Обычно так и делают. Т.е. фактически сам по себе CL предоставляет низкоуровневые средства для загрузки и обработки кода, а ASDF расширяет это до пригодных модулей. В Racket всё в коробке?

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

Fare говорит про какие-то другие «бессмысленные» комбинации, а их просто нет.

В смысле, «нет»? Вот полный список «бессмысленных» комбинаций:

(eval-when (:compile-toplevel) ...)
(eval-when (:compile-toplevel :load-toplevel) ...)
(eval-when (:load-toplevel) ...)
(eval-when (:execute) ...)
(eval-when () ...)

Эти все комбинации прекрасно компилируются, но (почти всегда) не имеют смысла.

И при чём тут вообще slime и asdf? defun ведь ничего не ломает.

При том, что выполняя код в slime ты будешь видеть то, что у тебя в (:compile-time :execute). Соответственно, часто будет, что в slime работает, а при загрузке с нуля — нет.

asdf — при том, что компиляция полная выполнит все эффекты в (:compile-time :execute), а инкрементальная — только в изменённых файлах. И если ты, например, решил засунуть defun внутрь (:compile-time :execute), так как используешь его только при раскрытии макросов, то при инкрементальной компиляции этот макрос будет раскрывать только в том файле, где был определён defun. Это как раз хороший пример, где приходится или удалять все fasl'ы.

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

Отладку делают именно в этом хаосе. И когда в «текущем расфаршированном» образе всё работает как надо, то мало кто повторяет все тесты для компиляции с нуля.

Ну да. Ну блин, а как по другому? За интерактивное программирование надо платить. Не перегружать же Lisp-машину каждый раз когда мы что-то в repl-е хотим выполнить.

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

В смысле, «нет»? Вот полный список «бессмысленных» комбинаций

Придерешься. :execute надо ставить всегда. Можно было бы конечно это сделать by default. Ну это так, мелкий недочёт в дизайне, не Бог весть какая печаль.

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

Ну да. Обычно так и делают.

Да ну? Если бы так делали, то C-c C-c в SLIME именно это бы и делал, а не запускал eval на одну форму.

В Racket всё в коробке?

Да. В общем-то низкоуровневые средства тоже есть, но чтобы эмулировать хаос CL, придётся всё писать в одном модуле. Граница модулей в Racket гораздо чётче, чем граница пакета в CL. В CL есть образ и я в любой момент могу влезть в любой пакет, причём в том числе на запись. В Racket можно считать, что образ — это модуль. После компиляции модуль сохраняется в «FASL» и для компиляции следующего модуля создаётся чистый образ, в который не попадают эффекты компиляции от других модулей. Кроме того, при нормальной работе (без явного отключения контроля безопасности) испортить чужой модуль нельзя. Его символы импортируются только на чтение. Это позволяет гарантировать инварианты при написании модуля. Например, если есть тип данных (например «сортированный массив», «список строк»), экспортируемый модулем и все функции, экспортируемые модулем сохраняют какой-либо инвариант («сортированность», «тип элемента»), то нет необходимости проверять истинность инварианта при получении данных этого типа.

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

Придерешься. :execute надо ставить всегда.

Ты первый начал :-) Fare сказал ровно это: что из 8 комбинаций параметров eval-when 5 бессмысленные, 1 не нужна, так как default, и 1 опасна при бездумном использовании с asdf. Остаётся одна: eval-always.

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

asdf — при том, что компиляция полная выполнит все эффекты в (:compile-time :execute), а инкрементальная — только в изменённых файлах. И если ты, например, решил засунуть defun внутрь (:compile-time :execute), так как используешь его только при раскрытии макросов, то при инкрементальной компиляции этот макрос будет раскрывать только в том файле, где был определён defun. Это как раз хороший пример, где приходится или удалять все fasl'ы.

Да, убедительно. Спасибо.

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

Ну блин, а как по другому? За интерактивное программирование надо платить.

Вот и я про то же. Подробнее изложено здесь: Анализ пользователей Common Lisp и Racket

Common Lisp приучает к хаосу в процессе разработки, отладке в REPL, использованию отладчика в продакшене. Как один товарищ писал «На других языках программы проектируют, на (Common) Lisp — выращивают».

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

Common Lisp приучает к хаосу в процессе разработки, отладке в REPL, использованию отладчика в продакшене. Как один товарищ писал «На других языках программы проектируют, на (Common) Lisp — выращивают».

Согласен. Но разработка сложный процесс, хаос неизбежен. «Выращивать» программу более естественный путь приводящий к хорошим интерфейсам, а проектирование (top->bottom) — путь ограничений и херовых интерфейсов (IMHO).

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

По крайней мере, начинать с «абы как» и допиливать постепенно до отличного состояние у меня получается лучше. А от «всё спроектировать, а потом написать» всегда терпел неудачу, в лучшем случае получается совместим с требованиями, но удовлетворения от конечного результата никакого.

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

Почему же не нужна? Можно использовать как комментарий, например. По остальным вариантам тоже не вижу проблем.

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