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.

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

★★★★★

В общем-то, тут уже напрашивающееся решение:

1. Пишем всё же N-1 ветвей - по числу переходов между версиями. Пусть это будут функции migrate-to-i-c1, где i - номер версии, куда мигрируем.

2. Добавляем поле «версия». К сожалению, это должно быть инициализуемое поле экземпляра. В качестве небольшого смягчения наказания, можно условиться, что отсутствие поля «версия» - это версия 0.

3. Метод для update-instance-for-redefined-class сначала по параметрам ориентируется, откуда и куда мы движемся.

4. Далее везде в теле метода вместо slot-value используем slot-or-externally-stored-value

(setf slot-or-externally-stored-value) пишет в слот, если он есть. Если слота нет, то в локальную переменную, определённую внутри нашего метода. Например, это может быть alist, где ключ - имя слота, а value - значение. И особый случай для unbound.

При чтении всё аналогично - если есть слот, читаем из него, если нет - из локальной переменной.

5. Выполняем все ветви от начальной до конечной версии.

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

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

trace update-instance-for-redefined-class

мляяяяяяяя. Вот это уродство так уродство. Оказывается лисперы не могут даже отличить объект от его имени. Полный абзац.

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

То что ты показал, это костыль для жаба-лалок, которые не могут отличать ссылки от значений. Это как бы намекает на *высокий уровень* коммон-лисперов. Особенно, в части понимания ООП. Воистину уродливый язык.

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

Ты создал класс с1, где «c1» - это имя глобального слота, ссылка на этот класс. если ты меняешь ссылку, «с1» начинает ссылаться на другой класс, при этом тот старый класс никуда не девается, от него по прежнему наследуют.

Для динамического языка это очень неудобно. Вот работает твоя программа: куча потоков, пользователей, все что-то создают, куча объектов. И в середине работы ты понимаешь, то в каком-то из классов не хватает нужного поля. Что будешь делать? В CLOS просто доопределишь класс, а всё остальное сделается автоматически. Причём, если это, например, ORM, то и структуру таблиц можно автоматически обновлять.

которые не могут отличать ссылки от значений

Куда уж тебе понять, если ты не можешь отличить класс от объекта (потому что в Io нет классов).

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

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

Внезапно, добавлю в этот класс нужное поле

если ты не можешь отличить класс от объекта (потому что в Io нет классов).

ты не можешь отличить семантику класса от синтаксиса класса.

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

В CLOS просто доопределишь класс, а всё остальное сделается автоматически.

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

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

Извини, мне довольно некогда. Я посмотрел 1 минуту, что такое Io. Если он также быстр, как LuaJit, то я посмотрю на него ещё 1 минуту. Ну или убеди меня, почему я ещё должен на него посмотреть.

На всякий случай знай моё мнение: статическая типизация нужна.

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

Не, он не быстрый. Его фишка в выразительности, и быстром построении DSL. Статической типизацией там и не пахнет. Судя по твоим хотелкам, тебе он не подойдет, забудь.

Но речь, собственно не о нем, а о CL. Даже в сраном JS ООП-модель более изящна.

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

Как правило — всегда. Уродливую модель нелегко контролировать, вместо цельной системы мы получаем миллион особых случаев. Ты в этом, собственно, уже убедился. Даже скоростью всегда надо жертвовать ради целстности и простоты, иначе в итоге не будет ни того ни другого.

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

Я правильно понимаю, что в твоём lo если все объекты созданы и программа запущена, но после этого если ты решил изменить класс то программа так и продолжит работу со старыми объектами того старого класса? Или для работы с новым определением класса все объекты при изменении принудительно становятся прототипами новых объектов?

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

Ты вообще не понимаешь архитектуру языков с поздним связыванием.

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

Что тебя конкретно интересует? При изменении класса все наследники тоже меняются. при этом ничего не апдейтится, никакие связи между объектами не затрагиваются. Это происходит из за того, что экземпляры получают свои свойства в рантайме из первоисточника. Информация всегда актуальна, ничего не копируется.

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

Это всё замечательно. Всё идеально и прекрасно. Но всё же расскажи мне, дебилу, каким образом совмещаются объекты старого класса в уже работающей программе с изменением класса и каким образом удаётся отойти от механизма сходного с тем же в CL?

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

Хотя это наверно можно устроить, если урезать описание объектов и сделать их всего лишь пачками хеш-таблиц или что-то на подобии на базе cons-ячеек. Для подобного в CL есть и хеш-таблицы и alist-ы, CLOS же для других вещей.

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

Я тебе сказал уже. Допустим, есть экземпляр foo класса Foo, где а = 1. при вызыве foo a интерпретатор сначала ищет слот с именем а в foo, затем в Foo. Из этого примера должно быть понятно, что происходит, и почему всегда и все актуально.

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

Хотя это наверно можно устроить, если урезать описание объектов и сделать их всего лишь пачками хеш-таблиц или что-то на подобии на базе cons-ячеек

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

если урезать

Если в CL объекты такие все неурезанные из себя, то неплохо было бы увидеть пример мощности этих неурезак. Покажи пример, как эта куча хлама, и набор костылей, которая там лежит, помогает тебе строить ООП - абстракции. Особенно смешно, учитывая, что CL даже не все объекты — полноценные объекты, с точки зрения Ъ.

Пока я вижу только то, что там набор неуклюжих костылей.

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

Т.е. ты хочешь сказать, что в lo после изменения класса объекты не строятся в шеренгу и эволюционировать там, а объект эволюционирует когда над ним начинаются какие-либо операции после изменения класса?

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

Примерно. Но никакого эволюционирования там вообще нет, все работает как обычно. Объект просто отвечает на сообщение. Изменение поведения класса автоматически меняет его поведение, уже в тот момент, когда изменился класс, так как его поведение напрямую зависит от поведения класса.

somequest
()

Поэтому мы должны будем хранить все ветви алгоритма обновления вечно.

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

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

Ух ты, кто-то ещё по делу пишет.

Поэтому мы должны будем хранить все ветви алгоритма обновления вечно.

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

Потому что эволюция может подразумевать изменение семантики. Сначала декартовы в скалярные, потом скалярные обратно в декартовы, с теме же именами x и y, но повернутый на 90 градусов. И так два раза. И вот тебе приходит инфа, о том, что у тебя был старый объект с полями x и y, а у нового объекта поля будут x и y. На сколько градусов повернуть? На 90 или на 180?

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

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

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

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

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

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

А CL — это не старинный лисп, а новый недолисп.

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

ты не выглядишь - ты и есть дурак

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

На всякий случай знай моё мнение: статическая типизация нужна.

чот странно слышать такое от коммон лиспера, лол.

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

Его фишка в выразительности, и быстром построении DSL.

можно пару примерчиков нетривиальных дслей на ио?

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

Допустим, есть экземпляр foo класса Foo, где а = 1. при вызыве foo a интерпретатор сначала ищет слот с именем а в foo, затем в Foo.

Допустим, у нас в Foo были поля a и b, в экземпляре foo a=1, b=2. При работе выяснилось, что нам часто нужно a и a+b. Добавляем поле c.

В какой момент c в поле foo станет равно 3?

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

Не знаю, как насчет нетривиальных, но вот то что на коленке за 5 минут накатал

JS := Object clone do(
   curlyBrackets := method(
      call message arguments map(asString split(":")) map(l, List with(l first, doString(l second))) asMap asObject
   )
   squareBrackets := getSlot("list")
   List squareBrackets := method(i, at(i))

   console := Object clone do(log := getSlot("writeln"))
   eval := method(
     doString( call message arguments asString asMutable\
      replaceSeq(".", " ")\
      replaceSeq("updateSlot", "setSlot")\
    ) 
   )
)

JS eval(

  a = {b: 1, c: "foo", arr: [1,"bar",3]}

  console.log( a.b, a.c, a.arr[0], a.arr[1], a.arr[2] ) // 1foo1bar3

)

Полностью синтаксис канешн не поддерживается.

вот еще нагуглил пару примеров http://ru-iolanguage.livejournal.com/4437.html http://ru-iolanguage.livejournal.com/4314.html http://www.chrisumbel.com/article/io_language_prototype

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

В момент добавления поля. Как квантовая запутанность примерно:)

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

В какой момент c в поле foo станет равно 3?

Тут надо отметить, что никакого собственного поля «с» в foo вообще нет. Оно наследуется от Foo делегированием, и получается в рантайме.

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

Я не люблю медленные языки. Ещё я не люблю языки, где «всё можно». Потому что когда я иду по дорожке, я не хочу, чтобы земля подо мной неожиданно разверзлась. Но, конечно, ты можешь и дальше продвигать свой io в теме, которую я создал.

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

Как обычно приполз анонiмус и насрал про io.

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

Единственное, где оно может быть полезно — это поиграться студенту. Да и то недолго. Потому как обычно(не в твоем случае), человеку вообще пох на синтаксис и на язык, если он понимает сам принцип.

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

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

Эта проблема решена с максимальной «изящностью»: классов нет, есть объекты и прототипы — связь на типа-родителя в каждом объекте. Когда свойство не найдено в объекте — оно ищется в прототипе, его прототипе и так далее. Всё. Копирование? Наследование? Не, не слышали.

Инстансов тоже нет, естественно.

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

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

ados ★★★★★
()

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

Как раз для решения этой задачи у update-instance-for-redefined-class есть аргументы added-slots и discarded-slots .

То есть весь алгоритм будет вида «если в added-slots есть полярные координаты вычислим их из остальных (слоты + property-list), если появились ещё какие-то координаты, то вычислим их из уже вычисленных и т.д.

В любой конкретный момент у тебя в update-instance-for-redefined-class только N ветвей, так как в последнюю версию может быть только N разных исходных версий.

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

Оно наследуется от Foo делегированием, и получается в рантайме.

То есть получить для foo.с значение 3, а для bar, у которого a=1 и b=1 значение bar.c=2 никак автоматически нельзя? Только вручную хранить весь список объектов класса Foo и перебирать и пересчитывать при необходимости?

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

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

Foo := Object clone do(
 c := method(a + b)
)

foo := Foo clone do(a := 1; b := 2)
bar := Foo clone do(a := 1; b := 1)


foo do(list(a,b,c) println) // list(1, 2, 3)
bar do(list(a,b,c) println) // list(1, 1, 2)

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

классов нет

Что по твоему настоящие классы? Io имеет first-class классы.

Копирование?

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

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

И где в твоем примере дсл? Давай более конкретно - покажи дсл для описания, например, конечного автомата.

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

человеку вообще пох на синтаксис и на язык, если он понимает сам принцип.

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

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

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

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

Язык, в каком то смысле, отражает твое мышление.

Все с тобой ясно. Больше вопросов нет.

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

То есть весь алгоритм будет вида «если в added-slots есть полярные координаты вычислим их из остальных (слоты + property-list), если появились ещё какие-то координаты, то вычислим их из уже вычисленных и т.д.

Я ж привёл пример по мотивам CLHS.

1.

 (defclass x-y-position (position)
     ((x :initform 0 :accessor position-x)
      (y :initform 0 :accessor position-y)))

Декартовы координаты x и y. Создадим объект p1(1,0) и забудем про него.

2.

 (defclass x-y-position (position)
     ((rho :initform 0 :accessor position-rho)
      (theta :initform 0 :accessor position-theta)))

Превращаем в полярные, ρ и r - переопределяем класс

3.

 (defclass x-y-position (position)
     ((x :initform 0 :accessor position-x)
      (y :initform 0 :accessor position-y)))

Надоели полярные, превращаем обратно в декартовы x и y, но повёрнутые относительно версии 1 на 90. Т.е., объект, к-рый в первой версии хранил (1,0) должен теперь хранить (0,1). Создадим объект p3(1,0) и забудем про него.

4. Превращаем опять в полярные ρ и r, ибо надоело.

5. Опять надоело, превращаем в декартовы и ещё на 90 поворачиваем. Т.е., если объект в версии 3 хранил 1,0, то он станет 0,1. Если он был в версии 1 1,0, то он станет -1,0.

А теперь - опа! Вспоминаем про объекты p1 и p3 и обращаемся к ним. Они пытаются проапгредиться. В качестве множеств изменённых слотов придёт пустота. И мы не сможем в update-instance-for-redefined-class понять, к какой версии относятся эти объекты, 1, 3 или 5.

Занавес.

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

Вообще, для твоей задачи, как мне кажется, идеально подошло бы множественное наследование. Допустим, все экземпляры имеют только поля x y, с определенными значениями, а обратится ты к ним можешь как к любому из классов Dekarte, Polar, NumberFuking, Etc

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

Какие то у тебя задачи детские пошли

Надо хранить, а не вычислять. Пусть у тебя вместо a+b будет some_long_calc(a,b), которое выполняется полминуты. В CLOS вычислится 1 раз (при update-instance-for-redefined-class) и запишется в слот

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

Допустим, все экземпляры имеют только поля x y, с определенными значениями

Дорого на каждое чтение угла тригонометрию считать.

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

Конечно это можно сделать и на Io. Но фишка тут наоборот в реактивности. Твоя проблема не будет решена, если значение каждый раз новое. Если это не так, нахрен вообще весь сыр-бор затевать, можно завести метод update, и обновлять все поля по определенному событию. Это банальное кэширование.

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