LINUX.ORG.RU

эволюция классового общества

 ,


2

3

Проблема такова (SBCL)

(defclass c1 () ((i :initform 1)))
(defparameter *i1* (make-instance 'c1))
(defclass c1 () ((i :initform 1)(j :initform (print "Ura"))))
(trace update-instance-for-redefined-class)
(defclass c1 () ((i :initform 1)))
(slot-value *i1* 'i)
  0: (UPDATE-INSTANCE-FOR-REDEFINED-CLASS #<C1 {253D0051}> NIL NIL NIL)
  0: UPDATE-INSTANCE-FOR-REDEFINED-CLASS returned #<C1 {253D0051}>

«Ura» не напечаталось вовсе. Умный рантайм оптимизировал процесс эволюции и класс сразу скакнул в своей эволюции из первобытно-общинного строя в социализм, минуя промежуточную версию. Если бы мы между изменениями класса обратились к сущности, то прогресс прошёл бы за два этапа.

Поэтому приведённый вот здесь

http://www.lispworks.com/documentation/HyperSpec/Body/f_upda_1.htm#update-ins...

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

Если мы потом захотим преобразовать полярные координаты ещё в какие-то, а потом ещё и ещё, то нам нужно будет написать не N ветвей на каждый этап эволюции, а N*(N-1), на скачок от любой версии к любой другой. Плюс может оказаться нетривиальной задачей понять, откуда и куда мы мигрируем. Впрочем, это не так уж и тяжело - (наверное) достаточно завести слот «номер версии», который будем заменять другим на каждом шаге. Типа такого:

(unintern 'c1)
(unintern '*i1*)
(defclass c1 () ((i :initform 1) (version1 :initform nil)))
(defparameter *i1* (make-instance 'c1))
(defclass c1 () 
  ((i :initform 1)
   (j :initform (print "Ura"))
   (version2 :initform nil)))
(trace update-instance-for-redefined-class)
(defclass c1 () 
  ((i :initform 1)
   (version3 :initform nil)
   ))
(slot-value *i1* 'i)

  0: (UPDATE-INSTANCE-FOR-REDEFINED-CLASS #<C1 {24134041}> (VERSION3) (VERSION1) (VERSION1 NIL))
  0: UPDATE-INSTANCE-FOR-REDEFINED-CLASS returned #<C1 {24134041}>

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

А теперь - почему у меня с этим проблемы. Я захотел довольно простую (казалось бы) вещь - поменять тип поля с числа на строку с сохранением данных. Данные должны преобразоваться в строку с помощью prin1-to-string.

Тут вопроса, собственно, нет. Это просто пока что мысли вслух :)

★★★★★
Ответ на: комментарий от somequest

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

Да не нужно наследование. Речь не про структуру, а про её динамическую эволюцию. Я всего лишь заметил, что в sql

alter table действует мгновенно (с поправкой на изолированность транзакций).

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

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

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

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

Надоели полярные, превращаем обратно в декартовы x и y, но повёрнутые относительно версии 1 на 90.

Когда я возмущался, что для разных объектов нельзя сделать методы с одинаковыми именами и разным количеством параметров, мне справедливо заметили, что в CL не принято разные сущности называть одинаково, а если количество параметров разное, значит методы делают что-то разное.

Так вот, если они повёрнуты, то они уже не x и y, а x1 и y1, например.

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

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

Скорее наоборот, есть гарантия, что не обновятся. Сохранение образа = завершение сборщика мусора + дамп памяти. Больше там ничего существенного не происходит.

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

Я всего лишь заметил, что в sql alter table действует мгновенно (с поправкой на изолированность транзакций).

Здесь слоты тоже добавляются мгновенно. А значения новых слотов в SQL ты всё равно будешь явно через UPDATE писать.

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

можно завести метод update, и обновлять все поля по определенному событию.

Ну вот. Если событие = доступ к полю, то ты изобрёл update-instance-for-redefined-class из CLOS.

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

мне справедливо заметили, что в CL не принято разные сущности называть одинаково

Ну и в чем «справедливость» этого замечания? В том, что помимо всего прочего еще и полиморфизм кастрировали?

а если количество параметров разное, значит методы делают что-то разное.

С чего бы это? Функции sum(1,2) и sum(1,2,3) делают что-то принципиально разное?

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

Ну вот. Если событие = доступ к полю, то ты изобрёл update-instance-for-redefined-class из CLOS.

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

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

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

(defclass c1 () ((i :initform 1)))
(defparameter *i1* (make-instance 'c1))
(defclass c1 () ((i :initform 1)(j :initform (print "Ura"))))
быть не должно. Они на уровне реализации смешали в одну кучу классы и их имена. Должно быть что то типа
(defclass c1 () ((i :initform 1)))
(defparameter *i1* (make-instance 'c1))
(setclass c1 () ((i :initform 1)(j :initform (print "Ura"))))
Если мы определяем новый класс под тем же именем, старый класс не должен перезаписываться.

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

Это все намекает на то, что в CL классы не являются первоклассными сущностями. Следуя этой логике в отношении функций, например, у нас должно было бы работать как-то так

(define (foo) (print 1))
(define bar foo)
(define (foo) (print 2))
(bar) ; 2
Это нонсенс.

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

Здесь слоты тоже добавляются мгновенно.

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

Скорее наоборот, есть гарантия, что не обновятся.

Печаль. И нет способа насильственно обновить. Хотя по идее в SBCL было что-то типа map-all-objects, но оно вроде не гарантировало ничего, а так, для развлечения.

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

(define (foo) (print 2))

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

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

Это всего лишь оборот речи

Since there are no classes, there's no difference between a subclass and an instance

Если нет классов, откуда взяться сабклассам? Нет классов, как отдельной сущности, имеется в виду.

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

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

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

Это все намекает на то, что в CL классы не являются первоклассными сущностями.

Аналогом (define bar foo) для классов будет что-то вроде (setf (find-class 'bar) (copy-object (find-class 'foo))). И в этом случае класс bar не будет зависеть от изменения класса foo.

А переопределение класса аналогично

(define (foo) (print 1))
(define (bar) (foo))
(define (foo) (print 2))
(bar) ; 2 -- это правильно!

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

Функции sum(1,2) и sum(1,2,3) делают что-то принципиально разное?

Если там не произвольное количество аргументов, а ровно 2 и ровно 3 аргумента, то да — принципиально разное. Иначе зачем ограничиваешь количество аргументов ровно тремя?

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

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

Такое поведение сделать можно, но оно менее практично. Всё равно, что сделать, чтобы при создании новой функции под тем же именем, вызовы этой функции в ранее написанных местах вызывали бы старую функцию (функция ведь тогда тоже должна «не перезаписываться»).

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

Аналогом (define bar foo) для классов будет что-то вроде (setf (find-class 'bar) (copy-object (find-class 'foo))). И в этом случае класс bar не будет зависеть от изменения класса foo.

Нет, это не аналог. В случае (define bar foo) ничего не копируется. Это имеет принципиальное значение.

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

Да, я прям кинулся писать для тебя разные всякие dsl'ы. Чтобы умилостивить тебя.

Ну ты же сам сказал что на ИО удобно писать дсли. ДСЛ для конечных автоматов - можно сказать классическая задача для подобных инструментов и при этом весьма элементарная, порядка десятков строк. Из твоего отказа следует, что на ИО - не элементарная, значит ИО не подходит для написания дслей. Так и запишем.

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

В случае (define bar foo) ничего не копируется

Копируется. Значение элемента foo в значение элемента bar. Хотя согласен, copy-object там лишнее.

> (defclass a () ())
#<STANDARD-CLASS A>
;; (setf (find-class ...) ...) пишется так:
> (ensure-class-using-class (find-class 'a) 'b)
#<STANDARD-CLASS B>
> (defclass a () ((foo :initarg :foo)))
ПРЕДУПРЕЖДЕНИЕ: DEFCLASS: Класс A (или его предок) переопределен, экземпляры
                устарели
#<STANDARD-CLASS A :VERSION 1>
> (make-instance 'a :foo 1)
#<A #x20DB1C8E>
> (make-instance 'b :foo 1)

*** - MAKE-INSTANCE: некорректная пара ключевое слово/значение :FOO, 1 в
      списке аргументов.
      Разрешенные ключевые слова: NIL

Как видим, класс a обновился, а он же по имени b — не обновился.

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

И нет способа насильственно обновить.

Есть: http://www.lispworks.com/documentation/HyperSpec/Body/f_mk_i_1.htm#make-insta...

Это не то, почитай, что там написано. И обрати внимание: там не написано, когда этот процесс должен _завершиться_. В реальности в SBCL обновление экземпляров - ленивое. Чтобы оно было не ленивым, нужно иметь возможность получить список всех экземпляров класса, а это затраты на его поддержание.

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

Это не то, почитай, что там написано.

Да, увидел. Ступил слегка.

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

Верно. Причём экземпляров класса и всех подклассов. Сделать можно (weak-hash + initialize-instance:after). В принципе, на фоне создания объекта, плюс значение в хэше — недорого.

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

Сделать можно, но должно ли это быть у всех классов? Значение в хеше - это не дёшево при наличии сборки мусора. Eq хеш-таблицы в CL работают по адресу объекта, а он меняется, отсюда лишние действия - нужно перехешировать.

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

Сделать можно, но должно ли это быть у всех классов?

Если не у всех, то красивее сделать через метакласс.

хеш-таблицы в CL работают по адресу объекта, а он меняется, отсюда лишние действия

Уверен? Не по постоянному числовому идентификатору?

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

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

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

Во-вторых, ГЦ как-то убирает мусор, следовательно должен иметь список _всех_ объектов. Хоть этот способ будет зависеть от реализации, зато не требуется расширять класс лишними слотами.

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

Если не у всех, то красивее сделать через метакласс.

Не понял. Как?

Уверен? Не по постоянному числовому идентификатору?

Да, выяснял в своё время. Это даже следует из стандарта.

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

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

И да, с ГЦ перебор - я тупанул. Всё делается в пределах стандарта и переносимо.

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