LINUX.ORG.RU

Проверки во время компиляции.

 , , ,


0

3

Предположим, что программа — интерфейс к БД. Насколько адекватно будет во время компиляции анализировать схему и на её основании генерировать классы, поля, контракты? Или хотя бы также (во время компиляции) проверять корректность написанных запросов.

Если неадекватно, то как правильно?

★★★★★

Насколько адекватно будет во время компиляции анализировать схему и на её основании генерировать классы, поля, контракты?

Почему неадекватно? Хотеть.

Или хотя бы также (во время компиляции) проверять корректность написанных

запросов.

Common SQL из LispWorks так и делает.

anonymous
()

Для CL прввильно что бы макрос раскрывался в код который проверяет, анализирует и генерирует. То есть макрос выбирает оптимальную стратегию раскрытия, но необходимую работу выполняет код который будет результатом раскрытия макроса. Но вобще твой вопрос нуждается в переформулировке потому как фигня какая-та.

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

Если работу можно надёжно и полностью выполнить при компиляции зачем её откладывать на выполнение?

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

Но вобще твой вопрос нуждается в переформулировке потому как фигня какая-та.

Сформулируем так: есть пример из http://marijnhaverbeke.nl/postmodern/#quickstart

(defclass country ()
  ((name :col-type string :initarg :name
         :reader country-name)
   (inhabitants :col-type integer :initarg :inhabitants
                :accessor country-inhabitants)
   (sovereign :col-type (or db-null string) :initarg :sovereign
              :accessor country-sovereign))
  (:metaclass dao-class)
  (:keys name))
The above defines a class that can be used to handle records in a table with three columns: name, inhabitants, and sovereign. In simple cases, the information above is enough to define the table as well:

(dao-table-definition 'country)
;; => "CREATE TABLE country (
;;      name TEXT NOT NULL,
;;      inhabitants INTEGER NOT NULL,
;;      sovereign TEXT,
;;      PRIMARY KEY (name))"
(execute (dao-table-definition 'country))

А теперь предположим, что таблица уже создана сторонней программой. Насколько Right Thing будет

(defclass country ()
  ((name :reader country-name))
  (:metaclass from-db-class))

который при компиляции будет распаковываться в вышепоказанное через запрос к СУБД?

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

Если работу можно надёжно и полностью выполнить при компиляции зачем её откладывать на выполнение?

Меня смущает необходимость перекомпиляции при изменении схемы БД. А также неочевидность в коде, откуда что берётся.

Будет примерно так:

(defclass country ()
  ((name :reader country-name))
  (:metaclass from-db-class))

(insert-dao (make-instance 'country :name "The Netherlands"
                                    :inhabitants 16400000
                                    :sovereign "Beatrix"))

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

который при компиляции будет распаковываться в вышепоказанное через запрос к СУБД?

Попробуй различать раскрытие макроса и компиляцию результа этого раскрытия это разные вещи.

Да, это плохой пример поскольку cl:defclass раскрывается в вызов mop-методов которые именно выполняются, а не компилируюся.

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

Зачем лишп, когда есть Java?

Зачем Java, когда есть Common Lisp?

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

Если работу можно надёжно и полностью выполнить при компиляции зачем её откладывать на выполнение?

Если. Оптимизация это частные случаи.

Да код в который макос раскрывается компилируется, внезапно.

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

Да, это плохой пример поскольку cl:defclass раскрывается в вызов mop-методов которые именно выполняются, а не компилируюся.

Согласен. Должно быть

(db-defclass country ()
  ((name :reader country-name)))

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

А ты это не сам придумал?

Что именно? Необходимость перекомпиляции? Если проверка наличия таблиц/полей происходит во время компиляции, то очевидно, что в случае ALTER TABLE на БД надо перекомпилировать (хотя бы для проверки, что на удалённое поле никто не ссылается, например).

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

Да код в который макос раскрывается компилируется, внезапно.

Так в зависимости от окружающей среды будем получать разный код. Эдакий autotools получается...

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

Но зачем тебе в лиспе жабавский Hibernate?:)

Интересно как они эту проблему обошли. Руками пробивают соответствие полей в объекте и таблице или какой-то (полу)автоматический генератор есть. Если второе, то я на верном пути :-))

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

N лет назад они напрямую с явовским байткодом химичили, после этого от них всего можно ожидать.

Если же отвлечся от макросов и компиляции, то для это в CL перехватывют change-class и сопутсвующие через MOP.

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

Так в зависимости от окружающей среды будем получать разный код.

Это зависит от надобностей и кривизны рук. Но вобще возможность этого суть CL.

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

Если проверка наличия таблиц/полей происходит во время компиляции,

Еще раз (третий, больше не буду) различай раскрытие макроса и компиляцию результата раскрытия. Это разные вещи и если не различать то расуждения получаются «о своем, одевичьем» непонятные.

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

для это в CL перехватывют change-class и сопутсвующие через MOP

Это если мы хотим сделать ALTER TABLE при изменении класса. А задача ровно обратная: сервер изменил таблицы, нам надо подстроиться.

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

Еще раз (третий, больше не буду) различай раскрытие макроса и компиляцию результата раскрытия.

Но ты же не будешь спорить, что раскрытие макросов происходит внутри фазы (eval-when :compile-toplevel) и не происходит при load-toplevel и execute.

То есть, если надо синхронизировать с базой, то придётся выполнять compile-file. Или хотя бы (eval '(db-defclass ...))

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

Но вобще возможность этого суть CL.

С одной стороны да. С другой стороны with-slots стребует явного указания списка полей, несмотря на то, что вариант with-all-slots пишется тривиально.

Но «необходимо, чтобы из кода было очевидно, какие переменные связаны в этой форме» (c).

Поэтому и спрашиваю не «возможно ли», а «как правильно». Для C# «правильно» было бы написать Wizard, который сгенерирует текст класса. Но в CL так вроде не принято. Но вот как принято, мне пока неясно.

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

А задача ровно обратная: сервер изменил таблицы, нам надо подстроиться.

Через MOP ты можешь менять класс и без defclass, это как раз не проблема.

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

Через MOP ты можешь менять класс и без defclass, это как раз не проблема.

Это немножко другой путь решения этой же задачи. Без макроса.

Можно и всю схему анализировать не при компиляции а при запуске. Но тогда, во-первых, тратится время. Во-вторых, имена функций нужны при компиляции или придётся использовать один дженерик на все поля.

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

А в чем проблема анализировать схему базы в какой-нибудь форме top-level, где будет уже явно напрямую вызван код после раскрытия макроса, если так уже очень неймется?

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

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

Это немножко другой путь решения этой же задачи. Без макроса.

Но нужен ли тебе макрос здесь.

Можно и всю схему анализировать не при компиляции а при запуске. Но тогда, во-первых, тратится время.

При запуска чего?

antares0 ★★★★
()
Ответ на: комментарий от dave
(analize-base) ;; функция, анализирующая базу

(let ((croatia (get-dao 'country "Croatia")))
  (setf (country-inhabitants croatia) 4500000) ;; здесь будет ошибка компиляции
  (update-dao croatia))
monk ★★★★★
() автор топика
Ответ на: комментарий от antares0

Оно работает и без eval.

(defmacro volatile () (get-universal-time))
(defun with-eval () (eval '(volatile)))
(defun without-eval () (volatile))

CL-USER> (with-eval)
3582241361
CL-USER> (with-eval)
3582241362

CL-USER> (without-eval)
3582241371
CL-USER> (without-eval)
3582241371
CL-USER> (without-eval)
3582241371

Не работает. Если результат экспанда должен зависеть от внешних условий, то без eval экспанд заново не делается.

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

А так ли страшна ошибка исполнения?

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

Первый путь. Мы генерируем пачку defclass со всеми акцессорами по заданной схеме. Предназначено это для отдельной внешней тулзы, создающей файл *.lisp, который надо потом самому подключить к проекту. Непонятно, зачем здесь макрос.

Второй путь. Через MOP. Создаем некий метакласс. Специальная явно вызываемая функция анализирует схему и запоминает отображение имен таблиц и слотов на таблицы и поля базы. Нет акцессоров, нет генерируемого defclass. Зато есть сплошная динамика, когда мы определяем слоты на лету. Можно в любой момент переанализировать схему и поменять отображение. Тоже неонятно, зачем здесь макрос.

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

Первый путь... Предназначено это для отдельной внешней тулзы, создающей файл *.lisp, который надо потом самому подключить к проекту.

А чем это лучше моего варианта с макросом? по крайней мере макрос при перекомпиляции вызовется всегда, а внешняя тулза — если не забудет пользователь.

Специальная явно вызываемая функция анализирует схему и запоминает отображение имен таблиц и слотов на таблицы и поля базы.

Всё равно её придётся выполнять в (eval-when :compile-toplevel). Или Проверки во время компиляции. (комментарий) работать не будет. По-моему трудозатрат больше, чем через макрос, профит неочевиден (скорость компиляции?).

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

Я не очень понимаю твой сценарий. Как он будет использоваться? В чем его сермяжный смысл? Ну, да ладно. Я бы, например, никого не стал слушать, если бы у меня была интересная задумка :)

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

Я не очень понимаю твой сценарий. Как он будет использоваться? В чем его сермяжный смысл?

Смысл в автоматическом формировании программы по окружению (в данном случае таблицам в БД). Аналогичный подход можно также использовать при FFI: макрос парсит *.h или через рефлексию опрашивает библиотеку и создаёт соответствующие функции.

А напрягает то, что при простоте реализации, таковых очень мало. Также как и привязка типов к именам символов: все пишут «это просто, ведь у символа есть properties», но ни одной рабочей реализации нет.

Вот и пытаюсь понять, то ли просто никому не надо (динамика и свобода превыше всего), то ли есть подводные камни. Если первое, то в Racket должны быть другие традиции (в конце концов, там есть контракты в языке, typed racked и импортированное из модуля запрещено переопределять).

monk ★★★★★
() автор топика

Нужно понимать, что основная проблема, присущая анализу схемы БД из макроса - это то, что БД является внешним ресурсом (по отношению к компилятору и исходникам). Использование БД при компиляции может иметь следующие проблемы: 1) БД может быть недоступна при компиляции, и из-за этого компиляция будет неудачной 2) Надо хранить логины/пароли для доступа к БД в исходниках. А они могут поменяться, например. Из-за этого старые версии кода могут стать некомпилируемыми 3) Скомпилированный код зависит от внешних сущностей, которые могут менятся независимо от кода. Обычно бывает полезно, когда можно собрать любую старую версию кода. 4) Компиляция может быть долгой из-за доступа к БД 5) Необходимо иметь доступ к БД с той машины, которая компилирует код

ИМХО, адекватным будет следующее: 1) Макрос анализирует файл с описанием схемы БД. Этот файл лежит рядом с исходниками и не зависит от наличия доступа к БД. 2) Отдельный инструмент (может быть, вызываемый из REPL) перегенерирует описание схемы БД.

Это позволит разнести разные фазы компиляции (фаза, вытаскиващая схему из БД и фазы, использующие схему БД) и обеспечит детерминированность компиляции.

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

то ли есть подводные камни

Подводные камни есть. В первую очередь - зависимость компиляции от внешнего окружения, что не есть удобно. Точнее, зависимость от внешнего окружения плоха в том случае, если инструменты сборки не позволяют воссоздать это окружение локально. Например, при сборке поднимать локальный экземпляр СУБД и использовать его при сборке - это терпимый вариант.

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

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

Просто то же самое можно сделать и в рантайме. И тогда не надо перекомпилировать все при изменении структуры базы.

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

1) БД может быть недоступна при компиляции, и из-за этого компиляция будет неудачной

Тогда и запуск будет неудачным.

2) Надо хранить логины/пароли для доступа к БД в исходниках.

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

3) Скомпилированный код зависит от внешних сущностей, которые могут менятся независимо от кода.

Так в этом и есть суть идеи. Только мне это кажется достоинством, а тебе — недостатком. Можно поподробней, почему это плохо?

Обычно бывает полезно, когда можно собрать любую старую версию кода.

БД + код = повторимый результат. Или зачем тебе заведомо нерабочий код (если он с текущим окружением не работает)?

4) Компиляция может быть долгой из-за доступа к БД

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

5) Необходимо иметь доступ к БД с той машины, которая компилирует код

Любой лисп содержит в себе компилятор. Почти любая лисп-программа распространяется в исходниках. Почти всегда время компиляции намного меньше времени работы. Что мешает скомпилировать её на машине, где она будет запускаться?

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

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

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

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

А чего тебе надо, вообще?

Наверное понять, почему «зависимость компиляции от внешнего окружения(, что) не есть удобно» (с) dmitry_vk

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

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

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

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

Имхо, стоит генерировать во время компиляции, но оставить возможность перегенерить (перекомпилировать?) во время исполнения (например, эту функцию/макрос повесить на соответствующий рестарт, или в ручную из репла). Всё-таки изменение схемы БД — нестандартная ситуация, можно и перекомпилировать.

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

Тогда и запуск будет неудачным.

ИРЛ часто компиляция может быть не там, где запуск.

Нет. В том же месте, что и для доступа из программы

Аналогично. Зачем человеку, который компилирует приложение, знать пароли доступа к моей БД? Не хочу я их ему давать. Так что полагаем, что их у него нет.

Так в этом и есть суть идеи. Только мне это кажется достоинством

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

Что мешает скомпилировать её на машине, где она будет запускаться?

Отсутствие доступа к этой машине?

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

только анализ будет в рантайме, а не во время компиляции (и ошибка тоже офк).

Если анализ во время компиляции, то ошибки нет. К моменту, когда доходит до (setf (country-inhabitants ...))) соответствующая функция уже создана во время анализа.

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

Видимо, это неудобно, если, например, ты не хочешь делиться исходниками

Это просто непристойное предположение (или я уже не на ЛОРе?)

Имхо, стоит генерировать во время компиляции, но оставить возможность перегенерить (перекомпилировать?) во время исполнения (например, эту функцию/макрос повесить на соответствующий рестарт, или в ручную из репла).

Вот ровно с этой мыслью я открывал тред. Перекомпиляция в процессе технически делается тривиально. Но мне очень интересно противоположное мнение, которое здесь озвучил dmitry_vk

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

ИРЛ часто компиляция может быть не там, где запуск.

В случае лиспа (а также питона, руби, ...)? Даже elisp в байткоде не распространяется.

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