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.



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

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

Главное, после окончания не забыть либо всё переписать хотя бы на define-api из named-readtable и юнит-тестов подобавлять.

Вообще все истории успеха про Common Lisp обычно звучат так «мы смогли быстро написать прототип на Lisp, потом переписали на C++/Java/C#». Чуть реже бывает «мы написали программу на Common Lisp, в эксплуатации был сбой, но так как у Lisp'а крутой дебаггер, то мы удалённо всё исправили без остановки технологического процесса». Первый вариант мне нравится больше. Второй подразумевает, что у пользователя под рукой есть лиспер, который всё может в случае чего исправить.

На самом деле писать надёжно на CL можно, но требует большой самодисциплины. Проще переписать готовый прототип на другой язык.

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

записывает её во временный файл, компилирует его и загружает результат.

Но при этом не перезапускает образ (как должен был бы для детерминированной компиляции).

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

Проще переписать готовый прототип на другой язык.

Ну если есть куча денег, времени и макак, то проще, да. Мне же проще довести прототип до продакшена и PROFIT!

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

Ну это смотря что считать компиляцией. Должен ахаха.

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

Мне же проще довести прототип до продакшена.

Я видел очень мало библиотек на лиспе, которые нормально отрабатывают все граничные случаи. Я когда писал advanced-readtable, то пока не перешёл на define-api вместо defun также упускал некоторые (достаточно маловероятные) случаи использования.

Я не говорю, что довести прототип до продакшена нельзя, но средств которые помогали бы в этом, в лиспе уже нет. Максимум — SBCL при оптимизации намекает на ошибки типов и можно повтыкать проверки времени выполнения и понаписать тестов. Анализатора кода нет, статических типов нет, даже константных данных нет. (defconstant +str+ «постоянная строка») не гарантироует того, что эта строка не изменится.

Ну и самая большая беда: зависимость от загруженных других пакетов. Например, у меня в пакете определяется метод cffi:expand-to-foreign-dyn, чтобы float и double автоматически приводились к нужному типу. Если у кого-то в коде была проверка через handler-case на верные передаваемый тип, то она внезапно перестанет работать, если мой пакет загржен (даже если не используется).

Поэтому гарантировать работу можно только в закрытой среде: с запрещённым REPL'ом и строго определённым списком загружаемых пакетов.

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

Поэтому гарантировать работу можно только в закрытой среде: с запрещённым REPL'ом и строго определённым списком загружаемых пакетов.

На продакшене, так и делаю. По остальным пунктам — все тестами конечно не покроешь, но обычно юзкейс для конкретной задачи позволяет покрыть нужными тестами и потом быть уверенным в 99%, что будет все работать. В случае проблем — фиксится.

С другими языками такая же история, где немножко хуже или лучше, но я не променяю удобства CL для прототипирования и потом для быстрого допиливания в продакшен ни но что другое.

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

На самом деле писать надёжно на CL можно, но требует большой самодисциплины.

По-моему субъективному опыту, надёжность на типовых задачах не хуже чем в любой другой динамики, но писать быстрее, легче. Я вообще считаю, что динамика имеет право на жизнь исключительно в виде Lisp.

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

На продакшене, так и делаю.

Да ладно, что серьёзно? CL и production? В нашем мире скобкофобии и нежелания хоть немного напрячься для увеличения производительности труда на среднесрочную перспективу это невозможно.

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

Я не говорю, что довести прототип до продакшена нельзя, но средств которые помогали бы в этом, в лиспе уже нет. Максимум — SBCL при оптимизации намекает на ошибки типов и можно повтыкать проверки времени выполнения и понаписать тестов. Анализатора кода нет, статических типов нет, даже константных данных нет. (defconstant +str+ «постоянная строка») не гарантироует того, что эта строка не изменится.

Ну ты, вероятно, говоришь про уж совсем critical safe области. Я не вижу в упор чем в это плане CL уступает Python, Ruby, Perl, Tcl, например. Да ничем не уступает. Даже безопаснее местами (если сильно не увлекаться метапрограммированием).

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

То есть для обычных требований CL и unittests достаточно.

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

Но Erlang ещё понять можно в телекоме. Но вот CL где именно в продакшене используется и почему именно CL? Очень интересно.

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

CL (LispWorks) используется там, где бы могли использоваться C++ или Java например. Понятно, что CL выигрывает в скорости прототип -> продакшен. Где именно? Там где нужно сделать как можно быстрее или тз меняется часто. А почему не CL собственно?

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

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

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

Я не вижу в упор чем в это плане CL уступает Python, Ruby, Perl, Tcl, например. Да ничем не уступает.

Для этих языков также целевая ниша: скрипты и прототипы. Ещё Visual Basic под оффтопик можешь вспомнить...

Разумеется, никакого смысла переписывать работающую программу с CL, например, на питон нет. Но перед продажей потребителю переписать прототип с CL (или питона) на C++ (или хотя бы Haskell или Racket) очень даже имеет смысл.

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

Там где нужно сделать как можно быстрее или тз меняется часто.

... и при выполнении программы доступен разработчик. При выполнении этих условий, согласен, CL вне конкуренции.

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

Я вообще считаю, что динамика имеет право на жизнь исключительно в виде Lisp.

Про Lisp согласен, про именно Common Lisp — не очень.

1. Не продуманный (или устаревший) стандарт. Про eval-when мы тут уже обсудили. Ещё есть разный порядок аргументов у elt и nth, разные стандарты именования у fdefinition, funcall, function-lambda-expression. Отсутствует стандарт на слабые ссылки, потоки. Нельзя сделать коллекцию, чтобы с ней работал elt.

2. Система пакетов. Символы приходится или писать через имя пакета (как рекомендует google) или они должны быть совсем уникальны (например с префиксом, зависящим от пакета). Если писать через имя пакета, то хочется имя пакета сделать покороче. Но имя пакета должно быть уникально. Мой advanced-readtable как раз был попыткой решить эту проблему хотя бы для себя — я вводил возможность локального псевдонима для пакета.

3. Процесс компиляции. ASDF — ужас. Fare пытался написать XCVB. Но распространения он не получил.

4. (Следствие пункта 1). Для каждого более-менее крупного фреймворка разработчики пишут свой «Lisp» на макросах на базе CL. Например, hu.dwim.*. Так как синтаксис дублирует стандартную библиотеку, то это очень бесит некоторых разработчиков.

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

Извините за нескромный вопрос, но как вы оцениваете свой опыт в написании программ со сложной архитектурой на CL такими методами?

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

Но перед продажей потребителю переписать прототип с CL (или питона) на C++ (или хотя бы Haskell или Racket) очень даже имеет смысл.

(или хотя бы Haskell или Racket)

Ты это серьезно? CL не угодил, а хаскель с ракеткок — норм. Попахивает фанбойством. :)

Если кастомеру нужен конечный продукт, без копания в кишках программы, зачем переписывать вообще? растрата времени и денег. Во вменяемых конторах на это не пойдут.

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

Ты это серьезно? CL не угодил, а хаскель с ракеткок — норм.

Именно потому, что хаскель с ракеткой идеологические наследники алгола. И в отличие от PHP, Perl, CL требуют писать структурированно и с явным описанием интерфейсов.

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

Если кастомеру нужен конечный продукт, без копания в кишках программы, зачем переписывать вообще?

Затем, что конечный продукт должен работать во всех возможных ситуациях, возможных у пользователя, а не только тех, которые были интересны программисту. Если явно не описывать интерфейсы, то почти наверняка будут ситуации, где программист решил, что данные имеют какие-то ограничения и написал алгоритм исходя из этого представления, но не вставил проверку на то, что данные действительно попадают в эти ограничения.

И без переписывания (хотя бы на том же CL, но правильно) пользователь потом спрашивает: что делать, если программа пишет:

The value NIL is not of type STUMPWM::FRAME. 
0: (SB-DEBUG::MAP-BACKTRACE 
    #<CLOSURE (LAMBDA # :IN SB-DEBUG:BACKTRACE) {10082AF3EB}> 
    :START 
    0 
    :COUNT 
    100) 
1: (SB-DEBUG:BACKTRACE 100 #<SB-IMPL::STRING-OUTPUT-STREAM 
{10082AF313}>) 
2: (STUMPWM::BACKTRACE-STRING) 
3: (STUMPWM::PERFORM-TOP-LEVEL-ERROR-ACTION 
    #<TYPE-ERROR expected-type: STUMPWM::FRAME datum: NIL>) 
4: (SIGNAL #<TYPE-ERROR expected-type: STUMPWM::FRAME datum: NIL>) 
5: (ERROR TYPE-ERROR :DATUM NIL :EXPECTED-TYPE STUMPWM::FRAME) 
6: (SB-KERNEL::OBJECT-NOT-TYPE-ERROR-HANDLER 
    #<unavailable argument> 
    #.(SB-SYS:INT-SAP #X7FFFF6E1ED80) 
    #<SB-ALIEN-INTERNALS:ALIEN-VALUE :SAP #X7FFFF6E1E900 :TYPE (* (SB-ALIEN:STRUCT 
                                                                  
SB-VM::OS-CONTEXT-T-STRUCT))> 
    (149 21)) 

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

но не вставил проверку

Почему же не вставил? В публичном API нужны проверки на валидность входных данных всегда, иначе ССЗБ.

пользователь потом спрашивает: что делать, если программа пишет:

В продакшене, при такой ошибке нужно писать стектрейс в логфайл и быстро валится с просьбой отослать баг репорт разработчикам.

Опять какие-то надуманные проблемы, если честно.

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

И в отличие от PHP, Perl, CL требуют писать структурированно и с явным описанием интерфейсов.

Явное лучше не явного. period.

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

В публичном API нужны проверки на валидность входных данных всегда, иначе ССЗБ.

В продакшене, при такой ошибке нужно писать стектрейс в логфайл и быстро валится с просьбой отослать баг репорт разработчикам.

То есть stumpwm несмотря на 8 лет разработки и 59 разработчиков так и не готов для продакшена. Я примерно про это и имел в виду, когда говорил, что приходится переписывать. Можно сравнить с xmonad — развивался с тех же пор, 18 разработчиков, стабильней на порядок.

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

То есть stumpwm несмотря на 8 лет разработки и 59 разработчиков так и не готов для продакшена.

Конечно не готов, он же для программистов сделан. Для остальных есть недогномы и недокеды. У меня STUMPWM вроде нормально работал, правда я его уже примерно 2 года не трогал, может и сломали что-нибудь. Только баги везде есть, и причем здесь CL — не понятно.

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

Причем, StumpWM пытаются поддерживать на разных реализациях CL, каждая из которых имеет свои глюки. Если бы поддерживали только, например SBCL, было бы стабильно как и в xmonad, который пишут только под GHC. Так, что сравнение не корректное. Я тоже пишу только на LispWorks и мне плевать на другие реализации: моя цель — стабильный продукт, а не глючащий набор байт на разных глюкодромах. Возьми любою коммерческую контору, всегда начинают пилить на одной реализации любого ЯП, а потом портируют, если смогут поддерживать в будущем.

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

разных реализациях CL, каждая из которых имеет свои глюки. Если бы поддерживали только, например SBCL, было бы стабильно как и в xmonad, который пишут только под GHC.

я думал что на это и существует стандарт. Или ни один лисп-транслятор не умеет нормально интерпретировать стандарт без глюков и дромов и нужно подстраиваться под каждый глюкодром?

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

я думал что на это и существует стандарт.

В стандарте CL (также как и в си и си++ (до недавнего времени)) нет взаимодействия с ОС: например сеть, треды и т.п., поэтому каждая реализация лепит свое.

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

Про Lisp согласен, про именно Common Lisp — не очень.

Не согласен. Если Racket или, например, Clojure навязывает top->bottom разработку, то в динамике нет никакого смысла и для прототипирования это подходит не очень. Я считаю, именно в виде CL динамика имеет смысл.

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

Не продуманный (или устаревший) стандарт.

Ну уж. Стандарт более продуманный, чем в CL, я ещё не встречал.

Про eval-when мы тут уже обсудили.

Угу. И пришли к выводу, что ничего магического eval-when нет.

Ещё есть разный порядок аргументов у elt и nth

Это мелочёвка уровня creat. На полёт не влияет.

Система пакетов. Символы приходится или писать через имя пакета (как рекомендует google) или они должны быть совсем уникальны (например с префиксом, зависящим от пакета). Если писать через имя пакета, то хочется имя пакета сделать покороче. Но имя пакета должно быть уникально.

Эта проблема есть во всех ЯП с модулями. Какого-то качественно иного подхода нет.

Процесс компиляции. ASDF — ужас.

Ну уж, ужас. На редких кейсах иногда ломается. Это не критично. Основную задачу вполне успешно решает.

разные стандарты именования у fdefinition, funcall, function-lambda-expression

Не очень понимаю, что тут разного? Ещё надо учесть, что CL ставил своей задачей некоторую совместимость и похожесть стека ранних Lisp-ов.

Нельзя сделать коллекцию, чтобы с ней работал elt.

Вот это, пожалуй, единственное достойное замечание. Действительно, не плохо бы иметь какой-то протокол итераторов, как в современных ЯП это обычно принято. Но на практике этот недостаток не сильно велик — в большинстве случаев достаточно стандартных коллекций, ну а когда не достаточно, ad-hoc решения, навроде fset, вполне можно использовать не очень напрягаясь.

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

А вот макросистемы, динамических переменных почти нигде нет. А CLOS, сигнального протокола, format-а вообще нигде и качественных высокопроизводительных компиляторов для такой-то сверхвысокоуровневой функциональности вообще нигде нет.

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

Эта проблема есть во всех ЯП с модулями. Какого-то качественно иного подхода нет.

Одна из причин моего перехода на Racket была именно в модулях.

Имя модуля может быть длинным и осмысленным. При использовании я просто пишу:

(require (prefix-in p: my-useful-utility-package)
         collections ; есть final и first, они же есть в iterate
         (rename-in iterate ([final iter-final] [first iter-first]))

(p:my-func ...)

Причём все эти переименования и префиксы локальны для данного модуля и, соответственно ничего не ломают. Ещё есть подмодули (иерархия модулей).

В CL есть никнеймы, но они почти бесполезны, так как глобальны для образа.

И ещё есть защита: если я в CL напишу

(defpackage :test
  (:use :cl :asdf))

(in-package :test)
(defvar *wild* t)
*wild*
, то что получу? :WILD. Причём никаких предупреждений не будет.

В Racket на (define чужой-символ ...) я получу внятную ошибку.

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

А вот макросистемы, динамических переменных почти нигде нет.

Макросистема Racket является строгим надмножеством макросистемы CL. Динамические переменные там тоже есть с чуть другим синтаксисом (называются «параметры»).

А CLOS, сигнального протокола, format-а

В Racket есть Swindle. В нём есть CLOS, аналог format в стиле CL. Сигнальный протокол (в смысле рестарты) в Scheme не приняты, но любой алгоритм с сигнальным протоколом я могу переписать в стиле Scheme/Racket практически в тот же объём строк. А если учесть наличие call/cc, который позволяет не сразу вызывать рестарт, а чуть позже, так всё ещё удобней (особенно для Web'а).

вообще нигде и качественных высокопроизводительных компиляторов

Racket работает практически на той же скорости, что и SBCL (если не считать режима с (safety 0), но в продакшен всё равно с safety 0 никто запускать не будет.

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

Fare весь в слезах от использования Java и Питона

Его кто-то приковал к компьютеру и заставил писать на питоне? По ссылке просто истерика какая-то...

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

, то что получу? :WILD. Причём никаких предупреждений не будет.

результатом будет T, только что проверил

SBCL 1.2.4.debian

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