понимание ООП Алана Кея
От: vaa  
Дата: 16.02.23 03:22
Оценка: -1
Вот он говорит:

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

Разве объект не может быть сообщением?
set x = CreateObject("сообщение")
call послать x

Почему нельзя просто применить к данным функцию?
let r = hash "some data"
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: понимание ООП Алана Кея
От: ylem  
Дата: 16.02.23 04:48
Оценка: +1
Вроде бы он о том, что ему представлялось, что объекты "обмениваются сообщениями" вместо, в лучшем случае "просят друг друга что-то сделать".
Re: понимание ООП Алана Кея
От: MadHuman Россия  
Дата: 16.02.23 05:41
Оценка:
Здравствуйте, vaa, Вы писали:

vaa>Вот он говорит:

vaa>

vaa>«Я жалею, что придумал термин «объекты» много лет назад,
vaa>потому что он заставляет людей концентрироваться на мелких идеях.
vaa>По-настоящему большая идея — это сообщения».

vaa>Разве объект не может быть сообщением?
может. твой пойнт по сути о — базовую абстракцию (объект) использовать для создания другой — сообщения.
его пойнт — о самой абстракции (сообщении).
Re: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.23 05:44
Оценка: 8 (3) +1
Здравствуйте, vaa, Вы писали:

vaa>Вот он говорит:

vaa>

vaa>«Я жалею, что придумал термин «объекты» много лет назад,
vaa>потому что он заставляет людей концентрироваться на мелких идеях.
vaa>По-настоящему большая идея — это сообщения».

vaa>Разве объект не может быть сообщением?
В мире Кея — нет. Кей представлял себе объекты как "отдельные компьютеры". То есть приложение в его модели — это сеть "компьютеров", каждый из которых — чёрный ящик со своим состоянием.
Обмениваются они сообщениями. У сообщения в такой метафоре собственного поведения быть не может.

vaa>
vaa>set x = CreateObject("сообщение")
vaa>call послать x
vaa>

Обратите внимание на то, что кеевским сообщениям в современной ОО-нотации соответствуют методы.
То есть в вашей записи сообщением является "послать", а x — это дополнительный параметр этого сообщения.
Выходит, в современной нотации ваш вопрос можно переформулировать как "разве объект не может быть методом?"
И получается так, что — нет, не может. Поверх ООП можно построить какую-нибудь систему RTTI или рефлексии, где могут быть объекты-дескрипторы элементов системы типов, в том числе и методов.
Но дескриптор метода — не то же самое, что и метод.

vaa>Почему нельзя просто применить к данным функцию?

vaa>
vaa>let r = hash "some data"
vaa>

Потому, что в мире кеевского ООП, нет никаких "данных" и никаких "функций". Это — осознанный выбора, следствие одного из основных дизайн-решений. Кей это решение называл homoiconicity — то есть использование ровно одной метафоры вообще для всего.
В его мире, к примеру, нет "скаляров" или "плоских данных". Чтобы прибавить к двойке пятёрку нужно отправить объекту "2" сообщение "прибавить" с аргументом "5", где "5" — это ссылка на объект-пятёрку.
В ответ на это сообщение двойка пришлёт ссылку на другой объект типа "число". И значением этого числа будет "7".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: понимание ООП Алана Кея
От: 4058  
Дата: 16.02.23 07:47
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Обратите внимание на то, что кеевским сообщениям в современной ОО-нотации соответствуют методы.


Поскольку Smalltalk полностью динамический, то Кей решил назвать "сообщением" — вызов метода, которого у объекта может не быть. Вроде как один объект посылает сообщение другому объекту, а тот уже сам определяет, что с этим делать, при этом в случае отсутствии метода/сообщения также возвращалось специализированное "сообщение":

В Smalltalk-80, если ни один метод не соответствует сообщению, объект по умолчанию возвращает сообщение doesNotUnderstand. Вызывающий объект может отреагировать на него, либо передать сообщение дальше, либо сигнализировать об ошибке. Класс также может переопределить действие по умолчанию и сделать что-то, кроме возврата doesNotUnderstand.

Re[2]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 16.02.23 08:14
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>В мире Кея — нет. Кей представлял себе объекты как "отдельные компьютеры". То есть приложение в его модели — это сеть "компьютеров", каждый из которых — чёрный ящик со своим состоянием.


Не обязательно.
Ящик может быть без состояния.


S>Обмениваются они сообщениями. У сообщения в такой метафоре собственного поведения быть не может.


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

И если для ящика с состоянием принципиально, чтобы в сообщениях фигурировало лишь его ID, то объекты без состояний можно передавать по-значению, т.е. создавая копии их.
В этом моменте абтракция Кея недостаточно чётко отделяет мух от котлет.
Re[2]: понимание ООП Алана Кея
От: Философ Ад http://vk.com/id10256428
Дата: 16.02.23 09:03
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>То есть в вашей записи сообщением является "послать", а x — это дополнительный параметр этого сообщения.

S>Выходит, в современной нотации ваш вопрос можно переформулировать как "разве объект не может быть методом?"

Идеи в смоллтолке изначально дурацкие:

SendMessage(target=Сезам, message="откройся")
SendMessage(target=дверь1, message="откройся")
Всё сказанное выше — личное мнение, если не указано обратное.
Отредактировано 16.02.2023 9:07 Философ . Предыдущая версия .
Re[3]: понимание ООП Алана Кея
От: σ  
Дата: 16.02.23 09:42
Оценка: +1 :)
S>>В мире Кея — нет. Кей представлял себе объекты как "отдельные компьютеры". То есть приложение в его модели — это сеть "компьютеров", каждый из которых — чёрный ящик со своим состоянием.

V>Не обязательно.

V>Ящик может быть без состояния.

Это называется тривиальный/вырожденный случай.
Re[3]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.23 10:12
Оценка: -1
Здравствуйте, 4058, Вы писали:

4>Поскольку Smalltalk полностью динамический, то Кей решил назвать "сообщением" — вызов метода, которого у объекта может не быть.

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

4>Вроде как один объект посылает сообщение другому объекту, а тот уже сам определяет, что с этим делать, при этом в случае отсутствии метода/сообщения также возвращалось специализированное "сообщение":


4>

4>В Smalltalk-80, если ни один метод не соответствует сообщению, объект по умолчанию возвращает сообщение doesNotUnderstand. Вызывающий объект может отреагировать на него, либо передать сообщение дальше, либо сигнализировать об ошибке. Класс также может переопределить действие по умолчанию и сделать что-то, кроме возврата doesNotUnderstand.

Совершенно верно. Поэтому, скажем, паттерн прокси/скелетон для удалённого исполнения в смолтоке не требует ни компилятора IDL, ни динамической генерации кода через рефлексию.
Плюс там по аналогии с лиспом, которым Кей открыто вдохновлялся при проектировании смолтока, код существует в виде данных.
Поэтому нет никакой проблемы, скажем, отправить блок кода на исполнение удалённому объекту — как это сделано в СУБД GemStone.
А также программным способом проинспектировать и поизменять программный код.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.23 10:21
Оценка:
Здравствуйте, Философ, Вы писали:
Ф>Идеи в смоллтолке изначально дурацкие:

Ф>
Ф>SendMessage(target=Сезам, message="откройся")
Ф>SendMessage(target=дверь1, message="откройся")
Ф>

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

Cезам откройся.
дверь1 откройся.

Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.23 10:25
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Не обязательно.

V>Ящик может быть без состояния.
Это частный случай более общей модели, в котором состояние ящика является пустым. В мире Кея решение иметь или не иметь состояние полностью отдаётся на откуп ящику.
В частности, у чисел состояние есть, и оно неизменяемо. Но никаких средств выразить эту неизменяемость, как и само наличие либо отсутствие состояния, в Smalltalk нету.

S>>Обмениваются они сообщениями. У сообщения в такой метафоре собственного поведения быть не может.

V>Но сообщение может содержать ссылку на другие ящики.
Это никак не противоречит тому, что я написал.

V>Т.е. ящики могут обмениваться другими ящиками.

Ссылками на другие ящики.

V>И если для ящика с состоянием принципиально, чтобы в сообщениях фигурировало лишь его ID, то объекты без состояний можно передавать по-значению, т.е. создавая копии их.

V>В этом моменте абтракция Кея недостаточно чётко отделяет мух от котлет.
Да, конечно. Никто и не говорит, что абстракция Кея является непревосходимой вершиной архитектурной мысли.

Современные мультипарадигменные языки безусловно удобнее смолтока для решения прикладных задач.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 16.02.2023 10:26 Sinclair . Предыдущая версия .
Re[4]: понимание ООП Алана Кея
От: Философ Ад http://vk.com/id10256428
Дата: 16.02.23 10:33
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Простите, это вот что было? Это то, как вы понимаете идею смолтока, или то, что должно было быть вместо смолтока?

S>Потому что в смолтоке то, что вы пишете, записывается примерно так, и совершенно не выглядит дурацким:
S>

S>Cезам откройся.
S>дверь1 откройся.


Бредом это становится, когда начинаешь сам декомпозировать и распределять роли. Тут серьёзный подвох в том, кто именно выполняет действие:
либо дверь открывается сама после подачи команды — смолтолк, либо кто-то дверь открывает.

В некоторых случаях идеи смолтолка работают:
task = computer.StartCompute(data)

, но в большинстве — нет, потому что тебе частенько нужно выполнить какой-то алгоритм над объектом. В таких случает объект является объектом, а не субъектом.
Всё сказанное выше — личное мнение, если не указано обратное.
Отредактировано 16.02.2023 10:34 Философ . Предыдущая версия .
Re: понимание ООП Алана Кея
От: Baiker  
Дата: 16.02.23 10:34
Оценка:
Здравствуйте, vaa, Вы писали:

vaa>Разве объект не может быть сообщением?


Может! И сообщением. И телеграммой. И ботинком. СЕЙЧАС ты зачем нам это всё рассказываешь? Ты считаешь, тут никто не знает про Смоллток?
Re[5]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.23 10:56
Оценка:
Здравствуйте, Философ, Вы писали:

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

А, вы про это — ну да, Кей явно переоценил ООП.
Ну так это и было полсотни лет тому назад. Тогда редко какая программа на смолтоке занимала больше пары страниц.
Проблемы с выделением ролей и избыточными обязанностями начинаются при росте объёма решаемых задач.
Так-то анемик выглядит куда как более продуктивной моделью, чем рич; и особенно хорошо, когда анемик ещё и поддерживается инфраструктурой языка и среды исполнения.
Скажем, в смолтоке невыносимо легко заменить арифметику целых на арифметику каких-нибудь BigNumber. Но зато сделать в нём математику над традиционными int64 столь же эффективной, как и в С/С++ уже значительно сложнее.
В той же Java эффективность анемик резко падает при выходе "дата обжектов" за пределы примитивных типов.
Оттуда там любовь ко всяким "ооо, давайте мы представим набор комплексных чисел в виде двух массивов double" и прочему шаманству.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: понимание ООП Алана Кея
От: vsb Казахстан  
Дата: 16.02.23 11:05
Оценка: 1 (1) +3
Здравствуйте, Sinclair, Вы писали:

S>В той же Java эффективность анемик резко падает при выходе "дата обжектов" за пределы примитивных типов.

S>Оттуда там любовь ко всяким "ооо, давайте мы представим набор комплексных чисел в виде двух массивов double" и прочему шаманству.

Не очень понял мысль. Проблема жавы в данном случае в отсутствии value object. Там любой объект это полноценная махина на хипе, с кучей доп расходов по памяти, с доп нагрузкой на GC. Поэтому вместо миллиона таких объектов выгодней создать два объекта-массива.

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

К анемик вроде это никакого отношения не имеет. Анемик это по сути вопрос — где писать код методов. В самом объекте, или в другом месте. Разные уровни абстракции.
Re[2]: понимание ООП Алана Кея
От: B0FEE664  
Дата: 16.02.23 11:39
Оценка:
Здравствуйте, Baiker, Вы писали:

vaa>>Разве объект не может быть сообщением?

B>Может! И сообщением. И телеграммой. И ботинком. СЕЙЧАС ты зачем нам это всё рассказываешь? Ты считаешь, тут никто не знает про Смоллток?

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

Чтобы прибавить к двойке пятёрку нужно отправить объекту "2" сообщение "прибавить" с аргументом "5", где "5" — это ссылка на объект-пятёрку.
В ответ на это сообщение двойка пришлёт ссылку на другой объект типа "число". И значением этого числа будет "7".

то вот без дополнительных постулатов считать, что ответным сообщением будет 7 нельзя. Потому что перед сообщением "прибавить" с аргументом "5", к объекту "2" может прийти сообщение от другого объекта "прибавить" с аргументом, например, "1", поэтому ответом будет "8", а не "7".

Вот откуда взялось последовательность исполнения? Его нет в описании от Алана Кея...
И каждый день — без права на ошибку...
Re[7]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.23 12:27
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Не очень понял мысль. Проблема жавы в данном случае в отсутствии value object. Там любой объект это полноценная махина на хипе, с кучей доп расходов по памяти, с доп нагрузкой на GC. Поэтому вместо миллиона таких объектов выгодней создать два объекта-массива.

Совершенно верно.

vsb>И эта проблема решается введением, собственно value object-ов, которые нам всё обещает оракл. Когда объект будет лежать на стеке или в плоском виде внутри массива, с нулём доп расходов, копироваться по значению и тд и тп. По большому счёту вопрос оптимизации.


vsb>К анемик вроде это никакого отношения не имеет. Анемик это по сути вопрос — где писать код методов. В самом объекте, или в другом месте. Разные уровни абстракции.

Ну, если говорить строго — то да, анемик это про методы, а value-обжекты это про размещение данных.
Тем не менее, эти вопросы несколько коррелируют.
Смотрите: у Кея всё живёт в хипе, включая объекты-числа. Даже если мы их специальной магией интернируем, всё равно они ведут себя как забоксенные Integer и Double в Java.
Со всеми вытекающими последствиями.
Хотим посчитать сумму массива чисел — бежим по массиву ссылок, и отправляем каждую из ссылок в объект-аккумулятор при помощи метода .+().
Анемика предлагает убрать метод + из чисел, а вместо этого иметь некий stateless сервис-объект, у которого есть метод +(number1, number2).
C этой точки зрения value-типы представляют собой экстремальное развитие идей анемики — у них нет своей VMT и идентичности; они описываются только своим состоянием, а, значит, их можно безопасно копировать и перемешать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: понимание ООП Алана Кея
От: Философ Ад http://vk.com/id10256428
Дата: 16.02.23 12:28
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>..для меня следует, что каждый объект может существовать в своей нитке — в своём потоке исполнения, а значит сразу отсутствуют последовательность выполнения...


В частности об этом я говорил, утверждая, что тебе частенько нужно выполнить какой-то алгоритм над объектом, и что в таких случаях объект является объектом, а не субъектом.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[3]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.02.23 12:31
Оценка:
Здравствуйте, B0FEE664, Вы писали:
BFE>то вот без дополнительных постулатов считать, что ответным сообщением будет 7 нельзя. Потому что перед сообщением "прибавить" с аргументом "5", к объекту "2" может прийти сообщение от другого объекта "прибавить" с аргументом, например, "1", поэтому ответом будет "8", а не "7".
Можно. Объект "2" — "вечен" и неизменен. Сколько бы раз мы ни прибавляли к двойке пятёрку, ответом всё равно будет ссылка на 7. Если кто-то прибавит к этой двойке 1, то этот кто-то получит в ответ ссылку на 3, что никак не помешает остальным прибавлять к двойке всё, что угодно.

BFE>Вот откуда взялось последовательность исполнения? Его нет в описании от Алана Кея...

АФАИР, Кей, в том числе, предполагал и возможность независимых потоков исполнения "внутри" объектов. Но я не слишком хорошо знаком с оригинальным смолтоком, чтобы осмысленно обсуждать вопросы многопточности.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: понимание ООП Алана Кея
От: B0FEE664  
Дата: 16.02.23 17:27
Оценка:
Здравствуйте, Философ, Вы писали:

BFE>>..для меня следует, что каждый объект может существовать в своей нитке — в своём потоке исполнения, а значит сразу отсутствуют последовательность выполнения...

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

Не обязательно. Сообщение может быть таким: примени к себе следующий код: "<некоторый код для исполнения>". Например, message: eval, data: "alert('Hello World');".
И каждый день — без права на ошибку...
Re[5]: понимание ООП Алана Кея
От: Философ Ад http://vk.com/id10256428
Дата: 16.02.23 18:00
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Не обязательно. Сообщение может быть таким: примени к себе следующий код: "<некоторый код для исполнения>". Например, message: eval, data: "alert('Hello World');".


Я имел ввиду такие штуки, как паттерн состояние. Там состояние имеют полный доступ к внутренним данным контекста. Контекст — объект, состояние — субъект.
Всё сказанное выше — личное мнение, если не указано обратное.
Отредактировано 16.02.2023 18:01 Философ . Предыдущая версия .
Re: понимание ООП Алана Кея
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.02.23 22:09
Оценка: 6 (3) +1
Здравствуйте, vaa, Вы писали:

vaa>Вот он говорит:

vaa>

vaa>«Я жалею, что придумал термин «объекты» много лет назад,
vaa>потому что он заставляет людей концентрироваться на мелких идеях.
vaa>По-настоящему большая идея — это сообщения».



vaa>Разве объект не может быть сообщением?

vaa>
vaa>set x = CreateObject("сообщение")
vaa>call послать x
vaa>

Может, более того, должен


vaa>Почему нельзя просто применить к данным функцию?

vaa>
vaa>let r = hash "some data"
vaa>

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

Ключевая идея Кея не в том, что есть объекты и сообщения, а в том что нет ничего кроме объектов и сообщений. Что все операторы (кроме передачи ссылки на объект) в языке можно свести obj.send(msg). Класс это тоже объект, метод класса — объект, стек вызовов и фреймы — тоже объекты. Большая часть SmallTalk написана на самом smalltalk.

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

Кстати идея в общем неплохая оказалась, большинство динамических языков (Python и Ruby особенно) взяли очень многое от smalltalk.
Re[2]: понимание ООП Алана Кея
От: vaa  
Дата: 17.02.23 00:18
Оценка:
Здравствуйте, Baiker, Вы писали:

B>Здравствуйте, vaa, Вы писали:


vaa>>Разве объект не может быть сообщением?


B>Может! И сообщением. И телеграммой. И ботинком. СЕЙЧАС ты зачем нам это всё рассказываешь? Ты считаешь, тут никто не знает про Смоллток?


Просто непонятно как это знание можно применить на практике.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[4]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 17.02.23 08:10
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Не обязательно.

V>>Ящик может быть без состояния.
S>Это частный случай более общей модели, в котором состояние ящика является пустым. В мире Кея решение иметь или не иметь состояние полностью отдаётся на откуп ящику.
S>В частности, у чисел состояние есть, и оно неизменяемо. Но никаких средств выразить эту неизменяемость, как и само наличие либо отсутствие состояния, в Smalltalk нету.

Ес-но.
Как и в любом ООП-языке, предоставляющем ср-ва сокрытия внутреннего состояния.


S>>>Обмениваются они сообщениями. У сообщения в такой метафоре собственного поведения быть не может.

V>>Но сообщение может содержать ссылку на другие ящики.
S>Это никак не противоречит тому, что я написал.

Это отвечает на вопрос коллеги, которому ты отвечал — могут ли сообщения быть объектами?
Сообщения могут содержать отсылки к объектам.
ИМХО, засилье управляемых языков несколько атрофировало способность части коллег ощущать границу м/у объектами и ссылками на них.


V>>И если для ящика с состоянием принципиально, чтобы в сообщениях фигурировало лишь его ID, то объекты без состояний можно передавать по-значению, т.е. создавая копии их.

V>>В этом моменте абтракция Кея недостаточно чётко отделяет мух от котлет.
S>Да, конечно. Никто и не говорит, что абстракция Кея является непревосходимой вершиной архитектурной мысли.

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


S>Современные мультипарадигменные языки безусловно удобнее смолтока для решения прикладных задач.


Смолтолк такой же мультипарадигменный, замыкания в нём описываются проще, чем в Common Lisp.

Я бы мог слегка поприкалываться и порассуждать на ту тему, что изначально мультипарадигменность не предполагалась, а она "получилась сама" в процессе доведения языка до уровня полезности, но я этого знать не могу. К тому же, язык разрабатывали несколько авторов, поэтому, упоротость одного из авторов не обязательно была определяющей — холодные головы инженеров-зануд порой вносят удачный баланс в качели мыслей инженеров-фантазёров. ))
Отредактировано 17.02.2023 8:19 vdimas . Предыдущая версия .
Re[4]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 17.02.23 08:17
Оценка:
Здравствуйте, σ, Вы писали:

V>>Ящик может быть без состояния.

σ>Это называется тривиальный/вырожденный случай.

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

Но на этом история опять не заканчивается — ящик может содержать неизменяемое "личное" состояние, но ссылаться на объекты с изменяемыми состояниями.

Слишком много вырожденных случаев, показывающих слабость первоначальной формулировки, не находишь? ))
Как сказал один известный Альберт: "Делай настолько просто, насколько это возможно, но не проще".

В общем, формулировку Кея нахожу неудовлетворительной/неполной.
В погоне за "перлом" сам себя обманул. ))
Re[5]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 17.02.23 08:32
Оценка: +1
Здравствуйте, Философ, Вы писали:

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


И в чем подвох? ))
ООП-абстракция не навязываей ролей в конкретном дизайне.

Разработчик сам решает какие сущности будут "активными", а какие "пассивными".
Re[5]: понимание ООП Алана Кея
От: σ  
Дата: 17.02.23 08:36
Оценка: +1 :)
V>>>Ящик может быть без состояния.
σ>>Это называется тривиальный/вырожденный случай.

V>Но на этом история не заканчивается — ящик может содержать неизменяемое состояние, влияющее на его неизменяемое поведение, заданное при конструировании ящика.

V>Это тоже вырожденный случай?

Нет.

V>Но на этом история опять не заканчивается — ящик может содержать неизменяемое "личное" состояние, но ссылаться на объекты с изменяемыми состояниями.


Так. И?

V>Слишком много вырожденных случаев, показывающих слабость первоначальной формулировки, не находишь? ))


Нет.
Re[6]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 17.02.23 11:06
Оценка:
Здравствуйте, σ, Вы писали:

V>>Но на этом история не заканчивается — ящик может содержать неизменяемое состояние, влияющее на его неизменяемое поведение, заданное при конструировании ящика.

V>>Это тоже вырожденный случай?
σ>Нет.

С т.з. ваших рассуждений — да. ))
Если на некоторую формулу забиндили аргументы-константы, она не превращается от этого в объект с состоянием.
Простейший пример — биндинг лямбды к константам, в том числе частичный биндинг.
Вполне себе в духе Смолтолка.


V>>Но на этом история опять не заканчивается — ящик может содержать неизменяемое "личное" состояние, но ссылаться на объекты с изменяемыми состояниями.

σ>Так. И?

И опять стирается грань м/у ящиком с состоянием и без.


V>>Слишком много вырожденных случаев, показывающих слабость первоначальной формулировки, не находишь? ))

σ>Нет.

Я тебе уже пару подсказок выдал, а ты так и не смог провести очевидные границы различных сценариев (их 3 непересекающихся), покрываемых ООП-парадигмой, делающих эту парадигму мультипарадигменной by design. ))

Никакой из этих сценариев не является вырожденным, каждый из них является неотъемлимой частью ООП-подхода к проектированию, и тем более КОП-подхода.
"Вырожденными" эти сценарии кажутся только из-за неполноты формулировки Кея, о чём я и сделал замечание.
Отредактировано 17.02.2023 11:08 vdimas . Предыдущая версия . Еще …
Отредактировано 17.02.2023 11:07 vdimas . Предыдущая версия .
Re[4]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 17.02.23 11:41
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

4>>Поскольку Smalltalk полностью динамический, то Кей решил назвать "сообщением" — вызов метода, которого у объекта может не быть.

S>Всё наоборот. Началось всё с сообщений, которые объект может обрабатывать по своему желанию.
S>И уже из этой идеи стала вытекать полная динамичность смолтока.

Который, тем не менее, пригоден для статической компиляции, чем и занимается его JIT. ))


S>Поэтому нет никакой проблемы, скажем, отправить блок кода на исполнение удалённому объекту — как это сделано в СУБД GemStone.


Это следствие другого принятого решения — компиляции кода под архитектурно-независимую VM, т.е. оно перпендикулярно ООП-парадигме.


S>А также программным способом проинспектировать и поизменять программный код.


А это следствие еще одного решения — хранения метаинформации вместе с кодом.


S>Плюс там по аналогии с лиспом, которым Кей открыто вдохновлялся при проектировании смолтока, код существует в виде данных.


Если взглянуть внимательнее, то видно, что авторы более вдохновлялись Фортом (за авторством Чарльза Мура).
Сам Форт — это, по-сути, "бинарная" версия Лиспа, работающая эффективнее Лиспа порой на порядок.
Ну и, меньше скобок, больше синтаксиса, чище код.
Но "неестественная" запись осталась. ))
Плюс расплатой за эффективность стало явное оперирование состоянием стека (вернее, это был способ резко повысить эффективность на технике тех лет).

Смолтолк, считай, довёл цепочку Лисп-Форт-... до логической завершённости.
Заодно позволил обкатать ООП-парадигму, показать её эффективность.
(на Форте экспериментировали с "объектами" с самого начала)

В итоге ООП выиграло гонку популярности у ФП по очевидным причинам: ООП включало в себя целиком все известные на тот момент парадигмы — структурную и функциональную.
И если, скажем, ФП представляло из себя набор ограничений, то ООП представляло собой набор инструментария, в т.ч. для разработки другого инструментария, т.е. парадигма заведомо разрабатывалась для "расширения", а не ограничения. Первая человеческая IDE на Смолтолке с кучей функциональности (например, первый в мире автоматический рефакторинг) — самое яркое тому подтверждение. ))

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

Взять даже наш давний спор об имутабельности vs const в С++.
Этот спор был изначально глуп с моей т.з., бо как можно сравнивать подмножество с полным множеством?
Через const в С++ можно выразить абсолютно все сценарии вокруг иммутабельности целиком, но это будет лишь небольшая вершинка айсберга всех сценариев, покрываемых const.
Зато наоборот — дудки. ))

И этот сомн остальных сценариев, выразимых через const, точно так же нужен и полезен.
И не только для защиты от дурака-программиста, но в т.ч. помогает компилятору генерить более оптимальный код, разрешает больше агрессии при оптимизации.
Отредактировано 17.02.2023 12:07 vdimas . Предыдущая версия . Еще …
Отредактировано 17.02.2023 11:43 vdimas . Предыдущая версия .
Re[5]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 17.02.23 12:05
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Но обратная польская запись осталась. ))

V>Смолтолк, считай, довёл цепочку Лисп-Форт-... до логической завершённости.
V>Заодно позволил обкатать ООП-парадигму, показать её эффективность.
V>(на Форте экспериментировали с "объектами" с самого начала)

Вдогонку, на Форте синтаксис вызова методов объекта выглядит примерно так:
аргументы объект метод

Сравнить с синтаксисом Смолтолка:
объект метод аргументы
Re[3]: понимание ООП Алана Кея
От: Baiker  
Дата: 17.02.23 14:15
Оценка: +1
Здравствуйте, vaa, Вы писали:

vaa>Здравствуйте, Baiker, Вы писали:


B>>Здравствуйте, vaa, Вы писали:


vaa>>>Разве объект не может быть сообщением?


B>>Может! И сообщением. И телеграммой. И ботинком. СЕЙЧАС ты зачем нам это всё рассказываешь? Ты считаешь, тут никто не знает про Смоллток?


vaa>Просто непонятно как это знание можно применить на практике.


Smalltalk же! Написали язык в соотв. с теорией. Бери и пиши. Но т.к. эти концепции обосрались при проверке временем, крайне непонятно, зачем СЕЙЧАС подымать эту пыль. Мальчик познаёт мир?? Ну давайте теперь во всё тыкать, что ты прочёл — лисп, смоллток, рефал, оберон, пролог, оккмл... не надоело труху перебирать?
Re: понимание ООП Алана Кея
От: Pzz Россия https://github.com/alexpevzner
Дата: 18.02.23 14:01
Оценка:
Здравствуйте, vaa, Вы писали:

vaa>

vaa>«Я жалею, что придумал термин «объекты» много лет назад,
vaa>потому что он заставляет людей концентрироваться на мелких идеях.
vaa>По-настоящему большая идея — это сообщения».

vaa>Разве объект не может быть сообщением?

Нет. Сообщения — это методы объектов.
Re: понимание ООП Алана Кея
От: zx zpectrum  
Дата: 19.02.23 21:24
Оценка: 1 (1)
Для начала представим себе некий unix–pipe, например такой:

$ ps aux | grep conky | grep -v grep | awk '{print $2}' | xargs kill


Заметим, что перед нами ни что иное, как граф активных сущностей ("процессов"), у каждого из которых есть вход и выход. То есть да, обменивающихся сообщениями.
Пример, однако, маргинален: граф всегда вырожден в цепочку, а "сообщения" нетипизированы.

Теперь рассмотрим какие-нибудь, например, диаграммы языка LabView:

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

Думаю, Алан Кей в первую очередь имел в виду переход от ментальной концентрации на потоке управления к потоку данных.
В таком случае объекты становятся активными сущностями (каждый в своем процессе, потоке, либо замультиплексирован на основе event loop).
Re[5]: понимание ООП Алана Кея
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 20.02.23 09:33
Оценка:
Здравствуйте, Философ, Вы писали:

Ф>Бредом это становится, когда начинаешь сам декомпозировать и распределять роли. Тут серьёзный подвох в том, кто именно выполняет действие:

Ф>либо дверь открывается сама после подачи команды — смолтолк, либо кто-то дверь открывает.

Ну так это вы сами должны сначала решить, как это работает, а уже потом решение переводить в код.

Любой из вариантов возможен, this.open(door) и door.action(open). Какой из них правильнее — вопрос некорректный.

Например, если у героя могут быть 100500 способов открывания двери(ногой, рукой, рукой в перчатке, палкой, ключом, осторожно, рывком, итд) то стоит рассмотреть первый

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

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

На самом деле вариантов реализации в коде тоже 100500. Самое главное — что бы стоимость чтения-изменения-использования была минимальной, в т.ч. вашими коллегами.
Re[5]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 22.03.23 05:32
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Это следствие другого принятого решения — компиляции кода под архитектурно-независимую VM, т.е. оно перпендикулярно ООП-парадигме.

Нет, не следствие. Тот же Паскаль от рождения компилировал в платформенно-независимый P-код, и это никак не помогает ему в реализации подобных сценариев.
Да, перпендикулярно — в том же Лиспе никакого ООП нет, а вот code as data есть. Оттуда и слизано в SmallTalk.
И вообще к компиляции во что бы то ни было это отношения не имеет — в gemstone по проводу едет не "скомпилированный код", а его AST представление.

V>А это следствие еще одного решения — хранения метаинформации вместе с кодом.

Это всё одно и то же решение — code as data.

V>Сам Форт — это, по-сути, "бинарная" версия Лиспа, работающая эффективнее Лиспа порой на порядок. \

Ну, я в форте не спец, но особых сходств с Лиспом не вижу.
V>Плюс расплатой за эффективность стало явное оперирование состоянием стека (вернее, это был способ резко повысить эффективность на технике тех лет).
Эмм, в Форте же вообще нет динамической памяти. Так что там нет ни неизменяемости, ни списков, ни сборки мусора, в общем, ничего из Лиспа в нём нет.

V>В итоге ООП выиграло гонку популярности у ФП по очевидным причинам: ООП включало в себя целиком все известные на тот момент парадигмы — структурную и функциональную.

V>И если, скажем, ФП представляло из себя набор ограничений, то ООП представляло собой набор инструментария, в т.ч. для разработки другого инструментария, т.е. парадигма заведомо разрабатывалась для "расширения", а не ограничения. Первая человеческая IDE на Смолтолке с кучей функциональности (например, первый в мире автоматический рефакторинг) — самое яркое тому подтверждение. ))
И смолток так и остался уделом кучки гиков. А популярность ООП обрело только с выходом плюсов.

V>Вдогонку, ООП-парадигма включила позже естественным образом даже модели из паралельных вычислений — модели акторов с мейлбоксами и модели конкурирующих сигналов.

V>Т.е. ООП-парадигма на сегодня является наиболее полной парадигмой из всех известных.
Это если смотреть на всё через призму ООП парадигмы. Скажем, Java на уровне 1.2 полностью реализует всю ООП парадигму.
Тем не менее, внезапно оказывается, что современное программирование не удовлетворяется одной лишь ООП парадигмой, и на рынке рулят мультипарадигменные языки, в которые проникли чисто-ФПшные штуки.

V>Через const в С++ можно выразить абсолютно все сценарии вокруг иммутабельности целиком, но это будет лишь небольшая вершинка айсберга всех сценариев, покрываемых const.

Напомните мне, как мне через const в c++ обеспечить неизменяемость аргумента, переданного мне в метод? Не запретить моему методу менять аргумент, а запретить внешнему коду менять мой аргумент?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 22.03.23 09:39
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

V>>Это следствие другого принятого решения — компиляции кода под архитектурно-независимую VM, т.е. оно перпендикулярно ООП-парадигме.

S>Нет, не следствие. Тот же Паскаль от рождения компилировал в платформенно-независимый P-код, и это никак не помогает ему в реализации подобных сценариев.

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

В любом случае, если даже такой Паскаль существовал (с возможностью сохранять и оперировать этим P-представлением), тогда и возможность соответствующая была.
Аргумент из разряда "было или нет реализовано" опять немного перпендикулярный.

Аналогично в VB — компиляция происходит в нейтив, но для отладки используется P-код в памяти, без сброса образа на диск.


S>И вообще к компиляции во что бы то ни было это отношения не имеет — в gemstone по проводу едет не "скомпилированный код", а его AST представление.


Что не требуется уже на уровне переносимых объектных файлов.


S>Да, перпендикулярно — в том же Лиспе никакого ООП нет, а вот code as data есть. Оттуда и слизано в SmallTalk.


Я уже говорил — из Форта, скорее, который являлся чем-то вроде бинарной версии Лиспа.
Натяжка Смолтолка на Лисп, таки, слишком смелая.
Зато на Форт прямая — под капотом была стековая VM с двумя стеками из Форта и такой же шитый код по результату работы JIT.
Это почему к эффективности Смолтолка были вопросы, из-за недостаточно умного JIT.

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


V>>А это следствие еще одного решения — хранения метаинформации вместе с кодом.

S>Это всё одно и то же решение — code as data.

Повторюсь, любой объектный файл (или архив их в объектной библиотеке) над переносимой платформой (например, llvm) представляет из себя то же самое.
Даже в Паскале, Си и С++.
К языку это перпендикулярно.


V>>Сам Форт — это, по-сути, "бинарная" версия Лиспа, работающая эффективнее Лиспа порой на порядок.

S>Ну, я в форте не спец, но особых сходств с Лиспом не вижу.

Сходство с Лиспом там в общей схеме работы интерпретатора, который двухфазный:
1. компиляция во внутренее представление;
2. исполнение этого внутреннего представления.

Собсно, эти два языка отшлифовали принципы построения интерпретаторов, выделив их в отдельный класс ЯВУ.

В Форте была еще третья фаза — сохранение полученного скомпиллированного образа, в т.ч. одной из опций сохранения было удаление метаинформации ("словарей" в терминологии Форта) и неиспользуемых процедур ("слов" в терминологии Форта), т.е. запуск сохранённого образа затем сразу переходил ко второй фазе, т.е. не требовал текстовых исходников, что и определило поплярность Форта во встраиваемом оборудовании. Собсно, это был первый успешный язык достаточно высокого уровня, применённый во встраивемых сценариях.
https://habr.com/ru/post/29967/

Далее сходство в организации контекста компиляции ("контекст" в терминах Лисп и "набор активных словарей" в терминах форта).
В Форте, в отличие от Лиспа, можно управлять "контекстом" явно.
В Лиспе только неявно.

Далее сходство в том, что код представляет из себя адреса символов и литералов.
Плюс в Форте численные литералы были представлены значениями, а не адресами.

Еще отличие Форта в том, что в коде хранится адрес исполняемой процедуры слова, в то время как в Лиспе хранился адрес метаинформации символа, т.е. Чарльз Мур убрал лишний уровень косвенности в процессе работы программы. Однако же, обратным поиском адреса в Форте запросто памятся на "слова". Т.е. Мур сделал размен эффективности оперирования метаинформацией на эффективность исполнения.

Посмотреть "код как данные" можно командой "see".
Любопытства ради выполни в любой онлайн Forth IDE программу:
see see


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


Подтверждаю.
Задолго до выхода этой статьи в 1996-м я однажды плюнул на кривые ассемблеры под i35/i38/i48/i51 архитектуры и наваял кросс-ассемблер за один рабочий день.
Это вместе с макросистемой.
И сам ассемблер получился намного мощнее, т.к. изкаробки шла возможность описывать вычисления формул времени компиляции, офигенно помогало — достаточно было изменить одну константу (точное значение частоты кварца в моём случае) и все остальные константы можно было расчитать по достаточно сложным формулам прямо во время компиляции.

Да и, до первых виденных ЭВМ прилично упражнялся на программируемом калькуляторе MK-61, это тот же Форт, вид сборку.
Поэтому, программирование на Форте чуть позже зашло как к себе домой. ))


V>>Плюс расплатой за эффективность стало явное оперирование состоянием стека (вернее, это был способ резко повысить эффективность на технике тех лет).

S>Эмм, в Форте же вообще нет динамической памяти.

Есть.
Оперирование динамической памятью явное.
Это самые базовые слова:
— "," — выделить ячейку в динамической памяти и скопировать туда значение с вершины стека;
— "@" — читать из памяти число адресу на вершине стека;
— "!" — писать число из второй ячейки стека по адресу на вершине стека.

Есть вариации шириной не только в слово, но и в полуслово, в байт, в двойное слово.

Со строками/массивами аналогично, копирование и т.д.
— "S=" — сравнение двух строк, заданных адресами на стеке и т.д.
— "-TH" — на стеке адрес массива и индекс, после исполнения на стеке элемент массива.


S>Так что там нет ни неизменяемости, ни списков, ни сборки мусора, в общем, ничего из Лиспа в нём нет.


В Форте можно описать произвольные структуры данных в памяти.

Сборка мусора в Лиспе, опять же, реализована по-разному.
В некоторых реализация сборка мусора вызывается каждый раз при исчерпании текущих свободных ячеек в предвыделенном пуле их, что может провоцировать (и провоцировала) сборку мусора даже когда потребности в этом не было, т.е. не было "забытых" объектов.
В других реализациях сборка мусора вызывается явно в рамках борьбы такими с холостыми срабатываниями.

В любом случае, с многопоточной сборкой мусора в современных VM этот подход имел мало общего.

Да и сборщик мусора консервативный, а не точный, как в Java или .Net.
Т.е. не перемещает данные (не уплотняет память).
Т.е., не помогла особо метаинформация.

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


V>>В итоге ООП выиграло гонку популярности у ФП по очевидным причинам: ООП включало в себя целиком все известные на тот момент парадигмы — структурную и функциональную.

V>>И если, скажем, ФП представляло из себя набор ограничений, то ООП представляло собой набор инструментария, в т.ч. для разработки другого инструментария, т.е. парадигма заведомо разрабатывалась для "расширения", а не ограничения. Первая человеческая IDE на Смолтолке с кучей функциональности (например, первый в мире автоматический рефакторинг) — самое яркое тому подтверждение. ))
S>И смолток так и остался уделом кучки гиков. А популярность ООП обрело только с выходом плюсов.

С выходом объектного Паскаля и первых версий VB от MS, скорее.
А да, еще купленый и допиленный до Visual FoxPro (объектность там была на механике COM).

В те года С++ обитал в стадии "Си с классами" и точно был уделом гиков.

Плюсы были "распробованы" сообществом ближе к середине нулевых.
И во многом благодаря COM/OLE/ActiveX/MFC (а позже ATL) в Windows.

Наверно, нет смысла напоминать, что положение Windows и MS на тот момент можно было считать исключительным.
Вся отрасль тянулась за успехом их Windows и Офиса.
Оба продукта активно переписывались с Си на С++ в 90-е.
Даже драйвера устройств. ))


V>>Вдогонку, ООП-парадигма включила позже естественным образом даже модели из паралельных вычислений — модели акторов с мейлбоксами и модели конкурирующих сигналов.

V>>Т.е. ООП-парадигма на сегодня является наиболее полной парадигмой из всех известных.
S>Это если смотреть на всё через призму ООП парадигмы. Скажем, Java на уровне 1.2 полностью реализует всю ООП парадигму.

Java изначально ограничила саму ООП-парадигму и .Net совершила ту же ошибку в первоначальном дизайне.
И только сейчас пытаются залатать пробелы.

Помнишь обсуждение хотя бы новых управляемых указателей на методы в .Net?

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

Это серьёзный косяк, как ни крути.
C++ эту ошибку не совершал.


S>Тем не менее, внезапно оказывается, что современное программирование не удовлетворяется одной лишь ООП парадигмой, и на рынке рулят мультипарадигменные языки, в которые проникли чисто-ФПшные штуки.


Повторяю в сотый раз — "чисто ФП-шные" штуки были в ООП чуть не в первых ООП-языках.
Например, в том же Алголе-68.
Т.е. функциональный тип как первоклассный в языке, лямбды.
Симула, кстате, была тоже лишь попыткой объектного расширения Алгола.

Да и Смолтолк приобрёл свои характерные черты не в первой версии 72, и даже не в 76, а только в 80.


V>>Через const в С++ можно выразить абсолютно все сценарии вокруг иммутабельности целиком, но это будет лишь небольшая вершинка айсберга всех сценариев, покрываемых const.

S>Напомните мне, как мне через const в c++ обеспечить неизменяемость аргумента, переданного мне в метод? Не запретить моему методу менять аргумент, а запретить внешнему коду менять мой аргумент?

Элементарно.
Для этого значение должно быть const в контексте внешнего кода.
Т.е. значение может быть const по всей иерархии "внешности", вплоть до объявления.
Или может приобретать признак const лишь с некоторой точки вычисления.
Это на выбор программиста.

Если начинать с момента объявления, то получаешь это:

Через const в С++ можно выразить абсолютно все сценарии вокруг иммутабельности целиком

Что будет лишь малой частью всех возможных сценариев по слишком очевидной причине (тут уже сложно не материться) — ввиду наиболее сильного наложенного на сценарий ограничения.

Странно, что мы вообще спорили тогда и сейчас вокруг подобной элементарщины...
Отредактировано 22.03.2023 10:25 vdimas . Предыдущая версия . Еще …
Отредактировано 22.03.2023 10:15 vdimas . Предыдущая версия .
Отредактировано 22.03.2023 9:46 vdimas . Предыдущая версия .
Отредактировано 22.03.2023 9:42 vdimas . Предыдущая версия .
Re[7]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 22.03.23 12:36
Оценка: +1 :)
Здравствуйте, vdimas, Вы писали:

V>Такой Паскаль лично я в руках не держал, держал Турбо-Паскаль, потом объектный, потом Дельфи.

RTFM: https://ru.wikipedia.org/wiki/P-%D0%BA%D0%BE%D0%B4
V>В любом случае, если даже такой Паскаль существовал (с возможностью сохранять и оперировать этим P-представлением), тогда и возможность соответствующая была.
V>Аргумент из разряда "было или нет реализовано" опять немного перпендикулярный.
Простите, но это бред.
V>Аналогично в VB — компиляция происходит в нейтив, но для отладки используется P-код в памяти, без сброса образа на диск.
Ну, так как мне воспользоваться этим кодом изнутри VB?
V>Что не требуется уже на уровне переносимых объектных файлов.
При чём тут "не требуется"?
Речь о том, как что устроено, и с чем связаны какие возможности.
V>Я уже говорил — из Форта, скорее, который являлся чем-то вроде бинарной версии Лиспа.
Продолжаете безудержно фантазировать.
V>Натяжка Смолтолка на Лисп, таки, слишком смелая.
При чём тут натяжка? Это прямая речь Алана Кея.

V>Зато на Форт прямая — под капотом была стековая VM с двумя стеками из Форта и такой же шитый код по результату работы JIT.

Какой "такой же"?
V>Это почему к эффективности Смолтолка были вопросы, из-за недостаточно умного JIT.
Опять пошёл какой-то бред.
V>Зато PostScript, который является развитием Форта, вполне себе ездит с устройства на устройство и прекрасно исполняется.
А пост-скрипт то тут при чём?
V>Повторюсь, любой объектный файл (или архив их в объектной библиотеке) над переносимой платформой (например, llvm) представляет из себя то же самое.
V>Даже в Паскале, Си и С++.
V>К языку это перпендикулярно.
Сколько бы раз вы ни повторяли, это не перестанет быть заблуждением.
Попробуйте реализовать на Паскале, Си, и С++ функцию, которая бы передавала пользовательский предикат на сервер для исполнения.
А потом для сравнения попробуйте реализовать то же самое на Lisp или SmallTalk.
Может быть, тогда до вас дойдёт, как такая возможность связана с языком.

V>Сходство с Лиспом там в общей схеме работы интерпретатора, который двухфазный:

V>1. компиляция во внутренее представление;
V>2. исполнение этого внутреннего представления.
С этой точки зрения более-менее похожи все языки, включая basic.

V>Еще отличие Форта в том, что в коде хранится адрес исполняемой процедуры слова, в то время как в Лиспе хранился адрес метаинформации символа, т.е. Чарльз Мур убрал лишний уровень косвенности в процессе работы программы. Однако же, обратным поиском адреса в Форте запросто памятся на "слова". Т.е. Мур сделал размен эффективности оперирования метаинформацией на эффективность исполнения.

Короче, общего примерно столько же, сколько у вороны с роялем.

V>Да и, до первых виденных ЭВМ прилично упражнялся на программируемом калькуляторе MK-61, это тот же Форт, вид сборку.

V>Поэтому, программирование на Форте чуть позже зашло как к себе домой. ))
Простите, но рассуждения про Форт не очень интересны в контексте ООП. А ваше очередное самолюбование не интересно совсем.


V>Оперирование динамической памятью явное.

V>Это самые базовые слова:
V>- "," — выделить ячейку в динамической памяти и скопировать туда значение с вершины стека;
V>- "@" — читать из памяти число адресу на вершине стека;
V>- "!" — писать число из второй ячейки стека по адресу на вершине стека.
Это, мягко говоря, совсем не то, что применяется в Лиспе или Смоллтоке.

V>В Форте можно описать произвольные структуры данных в памяти.

В ассемблере тоже. Это не делает ассемблер лиспоподобным языком.

V>Сборка мусора в Лиспе, опять же, реализована по-разному.

Это подробности. Она является неотъемлемой частью языка.

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

V>В других реализациях сборка мусора вызывается явно в рамках борьбы такими с холостыми срабатываниями.
Чегось?
V>В любом случае, с многопоточной сборкой мусора в современных VM этот подход имел мало общего.
Это вообще иррелевантно к данному обсуждению. Речь о языке, а не о реализации.
V>Да и сборщик мусора консервативный, а не точный, как в Java или .Net.
V>Т.е. не перемещает данные (не уплотняет память).
Чегось? Продолжается безудержный бред, да?
V>Т.е., не помогла особо метаинформация.
Прекрасно помогла.

V>Второй вариант без проблем может быть реализован в Форте на библиотечном уровне.

V>В Лиспе на библиотечном уровне никак, т.к. отсутствуют встроенные примитивы прямой работы с памятью и соотв. доступ ко внутренним кишкам интерпретатора.
V>(в кое-каких реализациях присутствуют, но это уже намного более поздние эксперименты в попытках привнести в Лисп свойства полее поздних ЯВУ общего назначения)
В лиспе сборка мусора является частью языка. Отсутствие примитивов прямой работы с памятью — это скорее плюс, чем минус.

V>С выходом объектного Паскаля и первых версий VB от MS, скорее.

Это в (бывшем) СССР. А в мире объектные версии Паскаля прошли практически незамеченными массовой публикой.
Попробуйте найти литературу по ООП с примерами на Паскале или VB.

V>Java изначально ограничила саму ООП-парадигму и .Net совершила ту же ошибку в первоначальном дизайне.

Мне даже интересно, какие именно аспекты ООП-парадигмы проигнорировала Java.
V>Помнишь обсуждение хотя бы новых управляемых указателей на методы в .Net?
V>А обсуждение типов-делегатов все нулевые года?
V>Надо ж было умудриться описать делегаты как объекты, что делегаты с одинаковой сигнатурой стали несовместимы. ))
Это, собственно, и есть попытки выйти за пределы ООП-парадигмы. В чистом ООП никаких делегатов нет. Есть интерфейсы с определёнными сигнатурами.
А несовместимость делегатов с одинаковой сигнатурой в рамках ООП не является самостоятельной особенностью. Она скорее связана с выбором между утиной типизацией и номинативной типизацией.
V>Это серьёзный косяк, как ни крути.
V>C++ эту ошибку не совершал.
Скажем, в С++ два одноимённых типа с "одинаковой сигнатурой" ничуть не более совместимы между собой, чем в дотнете.

Это в ФП подобные проблемы возникнуть не могут, т.к. там совместимость любых функций с одинаковой сигнатурой ставится во главу угла. А в лиспе так и вообще, одинаковость сигнатуры не является критерием.

V>Повторяю в сотый раз — "чисто ФП-шные" штуки были в ООП чуть не в первых ООП-языках.

V>Например, в том же Алголе-68.
V>Т.е. функциональный тип как первоклассный в языке, лямбды.
Мне кажется, вы слишком смело называете Алгол ООП-языком. Никакого ООП в нём не было.
V>Симула, кстате, была тоже лишь попыткой объектного расширения Алгола.
Как так — взяли ООП-шный алгол и попробовали привинтить к нему "объектное расширения"?
V>Да и Смолтолк приобрёл свои характерные черты не в первой версии 72, и даже не в 76, а только в 80.
:sigh:
http://wiki.c2.com/?AlanKaysDefinitionOfObjectOriented

V>Для этого значение должно быть const в контексте внешнего кода.

Не-не-не, Дэвид Блейн. Вот я пишу свой код. И в нём мне бы хотелось полагаться на то, что его вызывают не с чем попало, а с неизменяемым объектом.
V>Т.е. значение может быть const по всей иерархии "внешности", вплоть до объявления.
Внешний код ещё не написан — каким образом я смогу потребовать от него const на этом значении "по всей иерархии внешности"?

V>Это на выбор программиста.


V>Что будет лишь малой частью всех возможных сценариев по слишком очевидной причине (тут уже сложно не материться) — ввиду наиболее сильного наложенного на сценарий ограничения.
Ок, как видим, вы так и не поняли, что такое иммутабельность, и почему она ортогональна const.
V>Странно, что мы вообще спорили тогда и сейчас вокруг подобной элементарщины...
Да ничего странного — у вас отсутствует обучаемость. Годы идут, а вы так и не вышли за пределы своих заблуждений.
В качестве упражнения можете попробовать спроектировать на С++ пару абстрактных классов — изменяемый вектор и immutable вектор. В терминах дотнета это были бы IImmutableList<T> и IList<T>. И посмотрите, можно ли их отнаследовать друг от друга; можно ли получить один из другого при помощи модификатора const; понадобится ли вообще модификатор const при проектировании этих классов или при их использовании.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 22.03.2023 12:37 Sinclair . Предыдущая версия .
Re[4]: понимание ООП Алана Кея
От: SkyDance Земля  
Дата: 22.03.23 15:59
Оценка:
S>

S>Cезам откройся.
S>дверь1 откройся.


Это может выглядеть так:


    sesame ! open,
    door1 ! open.


... и оно так в самом деле выглядит. На вполне себе существующем языке программирования. Чертовски удобно, очень мощный подход.
Re[8]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 22.03.23 17:32
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Такой Паскаль лично я в руках не держал, держал Турбо-Паскаль, потом объектный, потом Дельфи.

S>RTFM: https://ru.wikipedia.org/wiki/P-%D0%BA%D0%BE%D0%B4

RTFM чего именно?
Что такое P-код? ))
Это тоже перпендикулярно языку.
Собсно, по твоей ссылке и говорится, что концепция была впервые реализована в другом языке.

Сам я впервые "вживую" курочил P-код в Бейсике ZX Spectrum.
Паскаль под ZX Spectrum компилял в нейтив.
Паскали под IBM PC ходили борландовый и от MS, в P-код не компиллировали.


V>>В любом случае, если даже такой Паскаль существовал (с возможностью сохранять и оперировать этим P-представлением), тогда и возможность соответствующая была.

V>>Аргумент из разряда "было или нет реализовано" опять немного перпендикулярный.
S>Простите, но это бред.

Медитировать до просветления, как грится.
Сам же привёл пример конкретной системы gemstone, т.е. имелась ввиду соответствующая инфраструктура в этой системе.


V>>Аналогично в VB — компиляция происходит в нейтив, но для отладки используется P-код в памяти, без сброса образа на диск.

S>Ну, так как мне воспользоваться этим кодом изнутри VB?

Языками более низкого уровня.
Или расписать некий фреймворк оперирования памятью в некоей библиотеке и сделать импорт функций в VB (есть такая возможность), чтобы иметь требуемый инструментарий.
Например, можно взять готовую либу CRT.


V>>Что не требуется уже на уровне переносимых объектных файлов.

S>При чём тут "не требуется"?

Потому что в объектных файлах содержится код и данные со всей сопутствующей метаинформацией.


S>Речь о том, как что устроено, и с чем связаны какие возможности.


Если требуется посылать код другому устройству, то требуется переносимость кода.
Я уже начинаю подозревать, что ты видишь возможность переносимости кода только в виде его AST-представления.
В то время как исторически переносимость обеспечивалась P-кодом, хотя он изначально и не назывался так, а назывался кодом для абстрактной машины.
Кнут тоже использовал абстрактную машину в своих "Искусствах программирования" (если ты читал эти труды).

У нас был коллективный курсовик на 3-м курсе на тему реализации абстрактной машины и минимальной операционки+компилятора для него.

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


V>>Я уже говорил — из Форта, скорее, который являлся чем-то вроде бинарной версии Лиспа.

S>Продолжаете безудержно фантазировать.

Провожу ликбез.


V>>Натяжка Смолтолка на Лисп, таки, слишком смелая.

S>При чём тут натяжка? Это прямая речь Алана Кея.

Хосподя, Алан Кей тот еще болтун, навроде тебя.
Причём, болтуном он стал гораздо позже... в попытках, не знаю, придать некую публичную окраску своей роли в этой работе.
Он работал над этой системой не один, слава богу.
Такие монстры как Голдберг служили неплохим противовесом этому, в общем-то, нубу-самоучке.

Изначально Смолтолк был попыткой описать схематику Симулы на основе того ограничения, что "объекты" обмениваются лишь "сообщениями".
Какой там в опу Лисп? ))
Система получилась убогой по быстродействию, поэтому была переработана унутре на манер Форта.

Просто ты не знаешь (или не понимаешь) о чём речь.
Похоже, ты не трогал толком ни Смолтолк, ни Лисп, ни Форт, не понимаешь внутренней механики этих языков, но пытаешься фантазировать.

Рассуждать о Лиспе можно было лишь в последней версии Смолтолка-80, когда добавили метаклассы.
ООП в виде CLOS привнесли в 70-х в виде стандарта, напомню, и это всего лишь эмуляция ООП на библиотечном уровне.
Но концепции обкатывались, не без этого.

Т.е. в изначальном Лиспе никаких метаклассов не было, как и не было в Лиспе ООП, ес-но, были только ф-ии как первоклассные объекты (сам код) и данные в виде однонаправленно связанных встроенных в систему списков.

Очередное твоё "слышал звон".
Просто ссылаться на Лисп однажды стало модно, бгг, и этот болтун не преминул.


V>>Зато на Форт прямая — под капотом была стековая VM с двумя стеками из Форта и такой же шитый код по результату работы JIT.

S>Какой "такой же"?

Курить, что есть шитый код.


V>>Это почему к эффективности Смолтолка были вопросы, из-за недостаточно умного JIT.

S>Опять пошёл какой-то бред.

Ликбез.


V>>Зато PostScript, который является развитием Форта, вполне себе ездит с устройства на устройство и прекрасно исполняется.

S>А пост-скрипт то тут при чём?

Пример развития Форта до переносимости м/у устройствами.
Наглядно показывает, что "переносимость" не берется из воздуха.
Что требует для своей реализации как некие св-ва вычислительной модели, так и заточенную под переносимость обвязку (инфраструктуру).
Это, блин, просто пример, хотя бы немного воткнув в который, ты бы избавился от многих своих дурацких вопросов и еще более дурацких попыток спора по элементарным вещам.
Таки, самообразованием принято заниматься самостоятельно, не испытывая терпение коллег.


V>>Повторюсь, любой объектный файл (или архив их в объектной библиотеке) над переносимой платформой (например, llvm) представляет из себя то же самое.

V>>Даже в Паскале, Си и С++.
V>>К языку это перпендикулярно.
S>Сколько бы раз вы ни повторяли, это не перестанет быть заблуждением.

Но доказать не сможешь? ))


S>Попробуйте реализовать на Паскале, Си, и С++ функцию, которая бы передавала пользовательский предикат на сервер для исполнения.


Не вижу технических проблем передать чистую ф-ию или чистое замыкание при наличии соотв. инфраструктуры.
В вопросе удалённого исполнения кода есть всего два требования:
— переносимость кода;
— переносимость данных.

Переносимость данных была реализована в рамках COM/OLE и CORBA.
Переносимость кода и вообще отправка на удалённое исполнение в рамках ActiveX-апплетов (но только в рамках одной архитектуры).

Более широкая переносимость кода была реализована в тех же Java-апплетах и .Net-апплетах.

Напомню, что MS компилятор С++ умеет компиллировать unamanaged C++ код (т.е. безо-всяких managed расширений) в чистый байт-код.
(тип генерируемого образа для режима С++/CLI задаётся опциями компилятора)


S>А потом для сравнения попробуйте реализовать то же самое на Lisp или SmallTalk.


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


S>Может быть, тогда до вас дойдёт, как такая возможность связана с языком.


Да не связана.
Такая возможность связана с представлением исполняемого кода, а это представление перпендикулярно языку.
Разве ты не видел интерпретаторов Си или Паскаля?

Похоже, у тебя каша в голове из св-в языков и конкретных их компиляторов/интерпретаторов.
Посмотри на Схему — вот тебе оптимизирующий компилятор Лиспа.
Были убраны возможности рантайм рефлексии кода.
Язык, по-сути, тот же.
Отличается представлением скомпиллированного кода, а значит, особенностями модели вычисления.


V>>Сходство с Лиспом там в общей схеме работы интерпретатора, который двухфазный:

V>>1. компиляция во внутренее представление;
V>>2. исполнение этого внутреннего представления.
S>С этой точки зрения более-менее похожи все языки, включая basic.

Все реализации языков, работающие на основе дополнительной исполняющей машинки.
Но не сами языки.
Не осенило еще? ))

Разве ты не видел компиллирующих Бейсиков?
Например, MS Visual Basic for DOS компиллировал в неплохо оптимизированный код (я курочил дизассемблером что он там порождает — да там офигенно, на уровне их же компилятора Си и Паскаля).


V>>Еще отличие Форта в том, что в коде хранится адрес исполняемой процедуры слова, в то время как в Лиспе хранился адрес метаинформации символа, т.е. Чарльз Мур убрал лишний уровень косвенности в процессе работы программы. Однако же, обратным поиском адреса в Форте запросто памятся на "слова". Т.е. Мур сделал размен эффективности оперирования метаинформацией на эффективность исполнения.

S>Короче, общего примерно столько же, сколько у вороны с роялем.

Общей была идея двухфазного интерпретатора и представления кода как данных.

Большинство реализаций Форта позволяют сделать декомпиляцию программы. Полученный текст мало отличается от исходного.

И это были первые в мире интерпретаторы, если что.

Просто Мур допилил идею Лиспа до более эффективной, открыв тем самым эпоху достаточно эффективных (в сравнении с Лиспом) интерпретаторов.
Стековая машинка и фаза компиляции интерпретатором в шитый код стали стандартом для реализации интерпретаторов де-факто.
И источником тут является Форт.
Лисп является источником лишь транзитивно, т.к. необходимость борьбы с недостатками Лиспа и породила Форт.

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


V>>Да и, до первых виденных ЭВМ прилично упражнялся на программируемом калькуляторе MK-61, это тот же Форт, вид сборку.

V>>Поэтому, программирование на Форте чуть позже зашло как к себе домой. ))
S>Простите, но рассуждения про Форт не очень интересны в контексте ООП.

А зря. ))
ООП в Форте реализуется так же как в Лиспе — через макросистему.

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


Напомню, что CLOS — это не более чем попытка стандартизировать библиотечные ООП-возможности.
В Лисп точно так же может сосуществовать сколько угодно много независимых ООП-подсистем библиотечного уровня.
Но! Могут возникнуть вопросы конфликтов идентификаторов макросов и ф-ий таких подсистем.
Именно поэтому потребовалась стандартизация ООП-подсистем в Лисп.

В Форте конфликты имён решаются более чем элегантно через словари, поэтому вопрос стандартизации ООП-расширений никогда не стоял.
Бери любое на вкус.

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

И насчёт "самолюбования" — я просто вижу любопытствующего чела, который, однако, не обладает необходимой инфрмацией, чтобы понять — почему оно происходило именно так.
Ты не понимаешь мотива принятия некоторых важных решений в IT в ретроспективе.
Проводимый ликбез тебя коробит, но это особенности твоего чванства и ничего более.
В интересные места ведь тебя отсылаю. ))
Было бы любопытно...


V>>Оперирование динамической памятью явное.

V>>Это самые базовые слова:
V>>- "," — выделить ячейку в динамической памяти и скопировать туда значение с вершины стека;
V>>- "@" — читать из памяти число адресу на вершине стека;
V>>- "!" — писать число из второй ячейки стека по адресу на вершине стека.
S>Это, мягко говоря, совсем не то, что применяется в Лиспе или Смоллтоке.

Ты спрашивал про динамическую память — тебе ответили.
Ключевое тут то, что с помощью базовых примитивов можно расписать сколь угодно сложную систему управления динамической памятью, если по каким-то причинам не устраивает данная изкаробки. В том числе запросто пишется консервативный GC с любой из политик работы, например, какие были приняты в различных реализациях Лиспа — автоматический вызов при исчерпании пула ячеек vs явные вызовы GC.


V>>В Форте можно описать произвольные структуры данных в памяти.

S>В ассемблере тоже. Это не делает ассемблер лиспоподобным языком.

Разумеется, речь идёт о выразительности такого описания.
Выразительность Форта при описании объектов выше выразительности Лиспа.
Это из-за особенностей макросистемы Форта — макросы полностью перехватывают выход первой фазы интерпретатора, т.е. в Форт изкаробки включены ср-ва оперирования кишками своей машинки, плюс доступны все слова, из которых состоит "компилятор" первой фазы. Поэтому ООП-расширения Форта смешны по объёму, т.к. вся инфраструктура по парсингу уже есть.

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


V>>Сборка мусора в Лиспе, опять же, реализована по-разному.

S>Это подробности. Она является неотъемлемой частью языка.

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


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

V>>В других реализациях сборка мусора вызывается явно в рамках борьбы такими с холостыми срабатываниями.
S>Чегось?

ЧТД. ))
А ведь это были важные отличия реализаций Лиспа.


V>>В любом случае, с многопоточной сборкой мусора в современных VM этот подход имел мало общего.

S>Это вообще иррелевантно к данному обсуждению. Речь о языке, а не о реализации.

Бгг...
Если GC не вызывается автоматом, то твои рассуждения "о языке" идут сразу в dev/nul.


V>>Да и сборщик мусора консервативный, а не точный, как в Java или .Net.

V>>Т.е. не перемещает данные (не уплотняет память).
S>Чегось? Продолжается безудержный бред, да?

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

Исходный GC связывал освобождаемые ячейки CONS в глобальный список свободных ячеек и не умел ничего более.
Перемещающи/копирующий GC должен уметь корректировать ссылки, а GC Лиспа той эпохи этого не делал.
(Не берусь утверждать за некие потенциальные новые реализации, бо за современным Лиспом не слежу, но десятилетиями оно работало именно так)

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


V>>Т.е., не помогла особо метаинформация.

S>Прекрасно помогла.

Не-а.


V>>Второй вариант без проблем может быть реализован в Форте на библиотечном уровне.

V>>В Лиспе на библиотечном уровне никак, т.к. отсутствуют встроенные примитивы прямой работы с памятью и соотв. доступ ко внутренним кишкам интерпретатора.
V>>(в кое-каких реализациях присутствуют, но это уже намного более поздние эксперименты в попытках привнести в Лисп свойства полее поздних ЯВУ общего назначения)
S>В лиспе сборка мусора является частью языка. Отсутствие примитивов прямой работы с памятью — это скорее плюс, чем минус.

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


V>>С выходом объектного Паскаля и первых версий VB от MS, скорее.

S>Это в (бывшем) СССР. А в мире объектные версии Паскаля прошли практически незамеченными массовой публикой.

VB зато широко использовался.
Visual FoxPro тоже, кстате.


S>Попробуйте найти литературу по ООП с примерами на Паскале или VB.


По VB и VFoxPro литературы мильон.


V>>Java изначально ограничила саму ООП-парадигму и .Net совершила ту же ошибку в первоначальном дизайне.

S>Мне даже интересно, какие именно аспекты ООП-парадигмы проигнорировала Java.

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


V>>Помнишь обсуждение хотя бы новых управляемых указателей на методы в .Net?

V>>А обсуждение типов-делегатов все нулевые года?
V>>Надо ж было умудриться описать делегаты как объекты, что делегаты с одинаковой сигнатурой стали несовместимы. ))
S> Это, собственно, и есть попытки выйти за пределы ООП-парадигмы.

Сотый раз повторюсь — этого не требовалось.
Приличный "почти современный" ООП был реализован еще до Смолтолка, в Алгол-68, и там был функциональный тип и лямбды, т.е. лямбда-исчисление.

Я уже подкидывал тебе мысль, что ФП-подход — это подход системы ограничений к процедурному программированию (функциональный тип был известен еще в эпоху процедурного программирования), в то время как ООП-подход — это инструментарий расширения.

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


S>В чистом ООП никаких делегатов нет. Есть интерфейсы с определёнными сигнатурами.


Мда... Будем разгребать:
Берём абстрактный функциональный ФП-тип.
Этот тип задан своей сигнатурой.
Для целей реализации лямбда-исчисления функциональный тип представлен кортежом { функция, данные }, где абстрактный тип лямбды определён сигнатурой функции.

В .Net это можно было бы описать так:
interface IFunc<TArg, TRet> {
    TRet Invoke(TArg arg);
}

Где данные передаются через неявно подаваемый this.
В ООП само понятие interface описано как кортеж таких функций.
Это прямо изначально по Барбаре Лисков, если что (главный теоретик ООП, а не этот ботун Алан Кей).
При сокращении кортежа до одной ф-ии получаем абстрактный функциональный тип.
Собсно, делегаты так и реализованы, т.е. никакого выхода за ООП-парадигму в них нет.
"Выход" за ООП-парадигму там можно усмотреть только в выразительности описания таких объектов, бо требуется описать всего лишь сигнатуру единственной ф-ии, и это будет сигнатура метода Invoke.

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


S>А несовместимость делегатов с одинаковой сигнатурой в рамках ООП не является самостоятельной особенностью. Она скорее связана с выбором между утиной типизацией и номинативной типизацией.


Ага, т.е. ты хорошо понимаешь про утиную типизацию, просто Ваньку тут валяешь.
Номинативная типизация — это тоже левое ограничение, не требуемое для реализации концепции ООП.
Это просто такое решение, ограничиться именно ею.
Хотя, базовая машинка способна вызывать методы не того объекта через свою систему команд, а вся "загвоздка" была бы только в верификации сгенерированного компилятором кода, верно?
Т.е. всего-то надо было снабдить верификатор возможностью утиной типизации неких типов, помеченных атрибутом как "делегат" (их даже не обязательно было наследовать от базового Delegate, достаточно было наследовать прямо от Object, а остальные ср-ва можно было бы получать от метакласса).

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

Все эти Action<> и Func<>, таки, выглядят крайне ущербно — как попытка залатать исходно присутствующую дыру во фреймворке.


V>>Это серьёзный косяк, как ни крути.

V>>C++ эту ошибку не совершал.
S>Скажем, в С++ два одноимённых типа с "одинаковой сигнатурой" ничуть не более совместимы между собой, чем в дотнете.

1. На шаблонном уровне совместимы.
2. Компилятор склеивает идентичный бинарный код различных типов.

А в дотнете ни ограничения в генериках на абстрактную сигатуру делегата не задать, ни описать эту абстрактную сигнатуру явно, скажем, где-то так:
bool Find<T>(ICollection<T> collection, bool predicate(T arg)) {...}


Об костыли Action<> и Func<> мы спотыкаемся сразу же, когда речь идёт о модификаторах, например ref/in/out, а недавно еще появились scope и readonly.
Плюс аргументами генериков не могут быть ref-структуры, поэтому, приходится описывать типы делегатов ручками как в старые добрые времена, когда генериков еще не было, бгг...


S>Это в ФП подобные проблемы возникнуть не могут, т.к. там совместимость любых функций с одинаковой сигнатурой ставится во главу угла.


Разумеется, но это же без проблем.
Просто считается, что любой функциональный тип с одной сигнатурой — он ровно один на всех.
Тут проблем нет.
И не зря в функциональных типах часто развита система алиасинга типов (аналог typedef в Си/С++).
Слава богу, в 2022-м появился global using в C#. ))
Пара десятилетий потребовалось для вменяемого алиасинга.

Но это должно было быть сразу, и под функицональынй тип должна была быть механика неявного алиасинга, как в ФП.

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

Например, по выходу дотнета за несколько вечеров я накатал компилятор ограниченной реализации Схемы.
Ты бы знал, как я матерился, выбирая представление функционального типа в конечном образе, ууу...
Зато освоил способы описания типов-делегатов "на лету", в процессе работы компилятора.
Разумеется, у меня был глобальный словарь всех уникальных сигнатур, встреченных программой.

Т.е., в наколенном варианте за несколько вечеров это решить можно, а на уровне инфраструктуры "забыли", угу...
При том, что F# был одним из первых реализованных языков.
Я не зря потом купил книгу одного из авторов дотнета, как они описывали процесс создания фреймворка, мне просто было интересно, как ои до такого дошли.
Книга написано вполне читабельно, но вызывает сожаление — они "тренировались на кошках" в основном в деле реализации C# и Питона.
При том, что Питон под дотнет так и остался невостребован.
Лучше бы разрабатывали его из F# и C#.


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


Ес-но, тут динамическая типизация vs статическая.
Но утиная типизация или нет перпендикулярна статической типизации, прикинь? ))
Порвал шаблончик или нет?
(опять и снова отсылаю к статически компиллируемым ФП-языкам)


V>>Повторяю в сотый раз — "чисто ФП-шные" штуки были в ООП чуть не в первых ООП-языках.

V>>Например, в том же Алголе-68.
V>>Т.е. функциональный тип как первоклассный в языке, лямбды.
S>Мне кажется, вы слишком смело называете Алгол ООП-языком. Никакого ООП в нём не было.

Я называю Алгол-68.
Это первый полноценный ООП-язык.


V>>Симула, кстате, была тоже лишь попыткой объектного расширения Алгола.

S>Как так — взяли ООП-шный алгол и попробовали привинтить к нему "объектное расширения"?

Ну да.
Берется язык со всем его синтаксисом и стандартной библиотекой.
Берутся исходники всего этого.
Допиливается.
Получается Симула, которая практически тот же Алгол.
Но работы была проведена недостаточно качественно в 67-м, поэтому появился Алгол-68.
На Симуле обкатали концепции, но недоделки стали видны сразу же — этот язык не мог претендовать на роль языка общего назначения.
Поэтому Алгол 68 уже мог.

Симулу могли бы назвать "Объектным Алголом".
Похоже, тебя чуть сбивает с толку совсем левое название языка. ))
Тот же Алгол, хосподя...


V>>Да и Смолтолк приобрёл свои характерные черты не в первой версии 72, и даже не в 76, а только в 80.

S>:sigh:
S>http://wiki.c2.com/?AlanKaysDefinitionOfObjectOriented

Мы тут говорили о коде как о данных.
Это появилось только с метаклассами в Смолтолке-80.
До этого никакой рефлексии не было.
Что касается ООП-парадигмы — она была уже давно обкатана до первых версий Смолтолка.
Смолтолк — это попытка выделить ООП-парадигму "в чистом виде".
Никакой практической пользы "чистота ООП" Смолтолка не имела до введения метаклассов аж в 80-м году, когда стало возможным проводить инспекцию кода, рефакторить и всё это было обёрнуто в IDE.

Поэтому, заслуга Смолтолка не в ООП-парадигме, а в развитой системе метаинформации об ООП-like объектах.
В возможности динамического создания типов и экземпляров этих типов.
А ты тут в очередной раз показываешь непонимание, откуда у чего растут ноги в IT. ))

Причина-следствие, причина-следствие... Это же важно, не?


V>>Для этого значение должно быть const в контексте внешнего кода.

S>Не-не-не, Дэвид Блейн. Вот я пишу свой код. И в нём мне бы хотелось полагаться на то, что его вызывают не с чем попало, а с неизменяемым объектом.

Да какие проблемы (мать, мать, мать...) — опиши сам объект как неизменяемый.
Тебе показать, как описывать иммутабельные объекты в С++?

Модификатор const к ссылке/указателю на объект разрешает вызывать только const-методы этого объекта.
То бишь, предполагается, что у этого объекта могут быть и не const-методы.

Я что в тот раз фигел от твоего непонимания сей элементарщины, что сейчас...

Заметь, в дотнете для структур ввели аналогичную семантику readonly для методов.
Поэтому, если структура передаётся по readonly ref-ссылке (кратно in), то можно вызывать только readonly методы.
В точности как в С++.
Неужели все вокруг идиоты и не понимают, ЗАЧЕМ это делается именно так? ))
Похоже, не понимаешь только ты, считая всех вокруг заранее идиотами.


V>>Т.е. значение может быть const по всей иерархии "внешности", вплоть до объявления.

S>Внешний код ещё не написан — каким образом я смогу потребовать от него const на этом значении "по всей иерархии внешности"?

Но тип аргумента-объекта уже должен уже быть? ))


V>>Это на выбор программиста.

S>

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


V>>Что будет лишь малой частью всех возможных сценариев по слишком очевидной причине (тут уже сложно не материться) — ввиду наиболее сильного наложенного на сценарий ограничения. S>Ок, как видим, вы так и не поняли, что такое иммутабельность, и почему она ортогональна const.


Мы видим несколько иное — чванливость Синклера, который иногда не в состоянии воспринимать информацию по причине ЧСВ и более ни по какой. ))


V>>Странно, что мы вообще спорили тогда и сейчас вокруг подобной элементарщины...

S>Да ничего странного — у вас отсутствует обучаемость. Годы идут, а вы так и не вышли за пределы своих заблуждений.

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


S>В качестве упражнения можете попробовать спроектировать на С++ пару абстрактных классов — изменяемый вектор и immutable вектор. В терминах дотнета это были бы IImmutableList<T> и IList<T>. И посмотрите, можно ли их отнаследовать друг от друга; можно ли получить один из другого при помощи модификатора const; понадобится ли вообще модификатор const при проектировании этих классов или при их использовании.


1. В дотнете я могу породить мутабельный тип из IImmutableList<T> аж бегом и со свистом.
2. В этой же технике в точности аналогично можно и в С++.

Ты не там гарантии иммутабельности ищещь.
Гарантии иммутабельности может иметь только сам тип.

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

Почему так? — по очевидной причине, ведь твой код тогда сможет работать как с тру-иммутабельными объектами, так и с неиммутабльными при рождении, но которые решили сделать иммутабельными "чуть позже". Например, некий достаточно сложно инициализируемый ветвистый словарь чего-то там.

В целях эффективности в момент наполнения словарь может быть мутабельным, а потом в какой-то момент владение передаётся константной ссылке/указателю и ву а ля.
В отсутствии const в этом сценарии потребуется два типа — мутальный и иммутабельный, где готовый мутабельный словарь копируется в иммутабельный, с соответствующими накладными расходами и прессингом на память. В С+ в этом сценарии достаточно скопировать ссылку/указатель.

Мне казалось, что ты понял эту особеность уже тогда, коль закончил пререкаться...
Но сейчас, смотрю, тебя всё еще распирает от врождённого упрямства "это ВЫ чего-то не понимаете, а не Я!!!111"
Значит, ни хрена ты не понял.
Походу, и не пытался, наивно надеясь, что всё понимаешь и так.
Не всё.
Отредактировано 22.03.2023 18:34 vdimas . Предыдущая версия . Еще …
Отредактировано 22.03.2023 18:23 vdimas . Предыдущая версия .
Отредактировано 22.03.2023 18:07 vdimas . Предыдущая версия .
Отредактировано 22.03.2023 17:46 vdimas . Предыдущая версия .
Отредактировано 22.03.2023 17:36 vdimas . Предыдущая версия .
Отредактировано 22.03.2023 17:33 vdimas . Предыдущая версия .
Re[4]: понимание ООП Алана Кея
От: Буравчик Россия  
Дата: 22.03.23 19:36
Оценка: +1
Здравствуйте, Baiker, Вы писали:

B>Smalltalk же! Написали язык в соотв. с теорией. Бери и пиши. Но т.к. эти концепции обосрались при проверке временем, крайне непонятно, зачем СЕЙЧАС подымать эту пыль. Мальчик познаёт мир?? Ну давайте теперь во всё тыкать, что ты прочёл — лисп, смоллток, рефал, оберон, пролог, оккмл... не надоело труху перебирать?


Концепции, реализованные в смоллтолке вовсе не "обосрались". Просто язык опередил свое время.

Где-то читал, что смоллтолк не получил распространения, потому что, во-первых, он был достаточно медленным (по сравнению со статически типизированными языками), а во-вторых, среды разработки для него были платными и очень дорогими (смоллтолк это полноценная среда разработки, а не просто интерпретатор).

В смоллтолке присутствовали элементы функционально программирования (функции высшего порядка), были отличные библиотеки работы с коллекциям (ага, как в C# сейчас, только еще лучше), любое поведение любого объекта можно было посмотреть, изменить. В языке был супер синтаксис — гибкий как лисп, но без огромного количества скобочек. Писать на нем легко, а отлаживаться — очень легко, потому что среда разработки это то же, что и среда исполнения.

А некоторые концепции так и не были повторены в других языках. Например, возможность изменять и сохранять состояние системы. Т.е. работающую программу можно остановить, сохранить, и позже восстановить и запустить. Или возможность изменять все элементы системы — "системные библиотеки" и т.п.
Best regards, Буравчик
Re[9]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 23.03.23 02:18
Оценка: :)
Здравствуйте, vdimas, Вы писали:
V>RTFM чего именно?
Истории Паскаля.

V>Сам же привёл пример конкретной системы gemstone, т.е. имелась ввиду соответствующая инфраструктура в этой системе.

Инфраструктура построена на каких-то основах.

S>>Ну, так как мне воспользоваться этим кодом изнутри VB?

V>Языками более низкого уровня.
V>Или расписать некий фреймворк оперирования памятью в некоей библиотеке и сделать импорт функций в VB (есть такая возможность), чтобы иметь требуемый инструментарий.
V>Например, можно взять готовую либу CRT.
Давайте пример кода на VB, который при помощи готовой либы CRT отправляет пользовательский предикат на сервер для исполнения.

V>Потому что в объектных файлах содержится код и данные со всей сопутствующей метаинформацией.

Это совершенно никак не связано с возможностями по манипуляции кодом как данными изнутри языка программирования.
Я не понимаю, почему эта очевидная вещь до вас не доходит. Компиляция в .obj была принята для практически 100% языков программирования; при этом возможностей мета-манипуляций в рантайме не было практически нигде.

V>Если требуется посылать код другому устройству, то требуется переносимость кода.

В первую очередь требуется возможность этот код описать в посылаемом виде.
V>Я уже начинаю подозревать, что ты видишь возможность переносимости кода только в виде его AST-представления.
Вы перескакиваете через ступеньку, пытаясь рассуждать об исполнении, как будто возможность породить такой код у вас есть.
Это во-первых.
Во-вторых, для систем типа GemStone возможность получить код в виде AST, а не байт-кода, является необходимостью.
Потому, что предикат не исполняется напрямую. СУБД на основе предиката строит план исполнения запроса, в котором одна часть предиката будет ключом поиска в индексе, другая будет фильтром по этому индексу, а третья будет вычисляться по связанной с индексом таблице. Это вам ликбез на пальцах.
Предикат, записанный в виде императивного байт-кода, таким преобразованиям поддаётся плохо либо никак.

V>В общем, твоя попытка рассуждать о банальностях как об откровениях малость сбивает меня с толку. ))

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

V>Провожу ликбез.



V>Хосподя, Алан Кей тот еще болтун, навроде тебя.

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

V>Такие монстры как Голдберг служили неплохим противовесом этому, в общем-то, нубу-самоучке.

А что, Голдберг где-то заявляла, что язык Smalltalk был вдохновлён Фортом?

V>Какой там в опу Лисп? ))

Да почитайте же уже The Early History of Smalltalk и перестаньте позориться.

V>Похоже, ты не трогал толком ни Смолтолк, ни Лисп, ни Форт, не понимаешь внутренней механики этих языков, но пытаешься фантазировать.

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

V>Рассуждать о Лиспе можно было лишь в последней версии Смолтолка-80, когда добавили метаклассы.

V>ООП в виде CLOS привнесли в 70-х в виде стандарта, напомню, и это всего лишь эмуляция ООП на библиотечном уровне.
V>Но концепции обкатывались, не без этого.


V>Т.е. в изначальном Лиспе никаких метаклассов не было, как и не было в Лиспе ООП, ес-но, были только ф-ии как первоклассные объекты (сам код) и данные в виде однонаправленно связанных встроенных в систему списков.

Конечно не было. Именно это Кей и реализовал — ему хотелось сделать систему, которая была построена на минимуме примитивов, как Лисп, но с ООП. Если бы в Лиспе было ООП, то Smalltalk бы и не понадобился.
V>Просто ссылаться на Лисп однажды стало модно, бгг, и этот болтун не преминул.


V>Курить, что есть шитый код.

Я знаю, что такое шитый код. Какое отношение он имеет к дизайну языка Smalltalk?

V>Пример развития Форта до переносимости м/у устройствами.

По-прежнему не понимаю, какое отношение Постскрипт с его переносимостью имеет к ООП Алана Кея.

V>Таки, самообразованием принято заниматься самостоятельно, не испытывая терпение коллег.

Ну так займитесь. Почему вы вместо того, чтобы почитать первоисточники, занимаетесь гаданием на МК-51?


S>>Попробуйте реализовать на Паскале, Си, и С++ функцию, которая бы передавала пользовательский предикат на сервер для исполнения.

V>Не вижу технических проблем передать чистую ф-ию или чистое замыкание при наличии соотв. инфраструктуры.
Пример кода в студию. Хотя бы на одном из языков.

V>Напомню, что MS компилятор С++ умеет компиллировать unamanaged C++ код (т.е. безо-всяких managed расширений) в чистый байт-код.

V>(тип генерируемого образа для режима С++/CLI задаётся опциями компилятора)
Ну ок, если вам будет проще — попробуйте изобразить требуемую функциональность на unmanaged C++, скомпилированном в чистый байт-код.


S>>А потом для сравнения попробуйте реализовать то же самое на Lisp или SmallTalk.


V>Ну вот попробуй на Лисп (или другом динамическом языке) без динамического исполнения текстовых исходников, а я поржу.

Посмотрите на то, как устроены макросы Лиспа. Там нет ничего про динамическое исполнение текстовых исходников.
Всё построено на манипуляциях кодом в виде AST. Можете начинать ржать.

V>Да не связана.

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


V>Похоже, у тебя каша в голове из св-в языков и конкретных их компиляторов/интерпретаторов.

Всё ровно наоборот — это у вас каша.

V>Посмотри на Схему — вот тебе оптимизирующий компилятор Лиспа.

V>Были убраны возможности рантайм рефлексии кода.
V>Язык, по-сути, тот же.
V>Отличается представлением скомпиллированного кода, а значит, особенностями модели вычисления.
И самое замечательное, что эти особенности модели вычисления никак не влияют на то, о чём я говорю.
Макросы в Схеме всё ещё есть, и это означает, что я могу решить обозначенную задачу и на схеме тоже.
А всё потому, что решают тут свойства языка. Что там под капотом — дело десятое.

V>Не осенило еще? ))

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

V>Например, MS Visual Basic for DOS компиллировал в неплохо оптимизированный код (я курочил дизассемблером что он там порождает — да там офигенно, на уровне их же компилятора Си и Паскаля).

Да видел конечно. Толку-то? Ни на одном из этих бейсиков я не могу отправить на сервер код пользовательского предиката.

V>В Лисп точно так же может сосуществовать сколько угодно много независимых ООП-подсистем библиотечного уровня.

Я в курсе. Синтаксис Лиспа настолько ужасен, что его ничем испортить нельзя. Поэтому всякие надстройки над ним выглядят не хуже оригинального лиспа.
И можно делать любое ООП, которое нравится — с наследованием интерфейсов и наследованием реализации, со множественным наследованием и без, с любыми наборами модификаторов "видимости" мемберов. Можно придумывать свойства и события; можно делать наследование экземпляров как в JS; можно обрабатывать неизвестные сообщения, как в Smalltalk.
Правда, всё это будет всё в том же ужасном стиле скобок поверх скобок. Но на фоне остального лисп-кода выделяться не будет.

В общем, Лисп считать ОО-языком бессмысленно.

V>Ты не понимаешь мотива принятия некоторых важных решений в IT в ретроспективе.


V>Проводимый ликбез тебя коробит, но это особенности твоего чванства и ничего более.
Скорее, это особенности вашего чванства.
V>В интересные места ведь тебя отсылаю. ))
Практически во всех этих местах я был. Вы что, всеръёз считаете, что я не в курсе кнутовского MIX-а, или никогда не слышал про Forth?

V>Ключевое тут то, что с помощью базовых примитивов можно расписать сколь угодно сложную систему управления динамической памятью, если по каким-то причинам не устраивает данная изкаробки. В том числе запросто пишется консервативный GC с любой из политик работы, например, какие были приняты в различных реализациях Лиспа — автоматический вызов при исчерпании пула ячеек vs явные вызовы GC.

Повторюсь: ООП-компилятор можно написать на не-ОО языке. Это ничего не говорит об ОО-свойствах этого языка.


V>Исходный GC связывал освобождаемые ячейки CONS в глобальный список свободных ячеек и не умел ничего более.

V>Перемещающи/копирующий GC должен уметь корректировать ссылки, а GC Лиспа той эпохи этого не делал.
V>(Не берусь утверждать за некие потенциальные новые реализации, бо за современным Лиспом не слежу, но десятилетиями оно работало именно так)
Наверняка вы что-то не так поняли. Весь смысл сборщика мусора — в возможности освободить занятую память.
Если сборщик ничего не перемещает, то откуда возьмётся освобождение? И вообще, зачем такой "сборщик" выполнять?
V>Не-а.
Может быть, я чего-то не понимаю. Вас не затруднит найти ссылку на описание того сборщика мусора, о котором вы рассуждаете?

V>По VB и VFoxPro литературы мильон.

Не по VB, а по ООП на VB. Примеры к "паттернам ООП" на каком языке даны?

V>Я говорю не про игнор, а про наложенные ограничения на реализацию парадигмы.

V>Джава наложила то ограничение, что экземпляры пользовательских типов будут всегда располагаться в динамической куче, а такого ограничения в исходной парадигме нет.
V>Это подробности конкретной реализации в неких принятых ограничениях.
Опять вы закапываетесь в подробности конкретной реализации. Зачем ?
В парадигме ООП нет ничего ни про кучу, ни про стек.

V>Сотый раз повторюсь — этого не требовалось.

V>Приличный "почти современный" ООП был реализован еще до Смолтолка, в Алгол-68, и там был функциональный тип и лямбды, т.е. лямбда-исчисление.

V>Я уже подкидывал тебе мысль, что ФП-подход — это подход системы ограничений к процедурному программированию (функциональный тип был известен еще в эпоху процедурного программирования), в то время как ООП-подход — это инструментарий расширения.

Это неудачная мысль.

V>ООП-подход включил известные на тот момент парадигмы целиком.

V>Дальнейшее развитие ООП-парадигмы и соотв. языков шло в области описания таких ограничений, коль они требуются, ср-вами языка.
V>Именно поэтому современные ООП-языки принято считать "мультипарадигменными", хотя это не так, разумеется.
Нет конечно.

S>>В чистом ООП никаких делегатов нет. Есть интерфейсы с определёнными сигнатурами.


V>Мда... Будем разгребать:

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

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

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


S>>А несовместимость делегатов с одинаковой сигнатурой в рамках ООП не является самостоятельной особенностью. Она скорее связана с выбором между утиной типизацией и номинативной типизацией.

V>Ага, т.е. ты хорошо понимаешь про утиную типизацию, просто Ваньку тут валяешь.
Конечно понимаю.
V>Номинативная типизация — это тоже левое ограничение, не требуемое для реализации концепции ООП.
V>Это просто такое решение, ограничиться именно ею.
V>Хотя, базовая машинка способна вызывать методы не того объекта через свою систему команд, а вся "загвоздка" была бы только в верификации сгенерированного компилятором кода, верно?
V>Т.е. всего-то надо было снабдить верификатор возможностью утиной типизации неких типов, помеченных атрибутом как "делегат" (их даже не обязательно было наследовать от базового Delegate, достаточно было наследовать прямо от Object, а остальные ср-ва можно было бы получать от метакласса).
V>Плюсом было бы то, что загрузчик-верификатор мог бы эффективно "склеивать" попадающие под утиную типизацию такие функциональные типы, т.е. не засорять память копиями одного и того же бинарного кода!
V>Типов-делегатов ведь овердохрена, но уникальных сигнатур потенциально меньше.
V>Все эти Action<> и Func<>, таки, выглядят крайне ущербно — как попытка залатать исходно присутствующую дыру во фреймворке.
Все эти Action<> и Func<> как раз и являются реализацией ровно вашей же идеи путём повторного использования уже существующих компонентов инфраструктуры. То самое склеивание функциональных типов и всё такое.

V>1. На шаблонном уровне совместимы.

Шаблоны тоже не являются частью ООП-парадигмы. Вы критикуете решение, принятое в системе, где шаблонов не было, при этом указывая на систему, где аналогичная дыра заклеивается при помощи шаблона
V>2. Компилятор склеивает идентичный бинарный код различных типов.
Это вообще деталь реализации.

V>А в дотнете ни ограничения в генериках на абстрактную сигатуру делегата не задать, ни описать эту абстрактную сигнатуру явно, скажем, где-то так:

V>
V>bool Find<T>(ICollection<T> collection, bool predicate(T arg)) {...}
V>

Не понял, а в чём тут проблема?

V>Об костыли Action<> и Func<> мы спотыкаемся сразу же, когда речь идёт о модификаторах, например ref/in/out, а недавно еще появились scope и readonly.

V>Плюс аргументами генериков не могут быть ref-структуры, поэтому, приходится описывать типы делегатов ручками как в старые добрые времена, когда генериков еще не было, бгг...
Это всё не относится к вопросам ООП парадигмы, а скорее о проектировании реальной платформы в условиях противоречивых ограничений.

V>Но утиная типизация или нет перпендикулярна статической типизации, прикинь? ))

Я в курсе.

V>Мы тут говорили о коде как о данных.

V>Это появилось только с метаклассами в Смолтолке-80.
V>До этого никакой рефлексии не было.
Рефлексия ортогональна коду-как-данным. В лиспе есть код-как-данные, но рефлексии может и не быть.
В раннем дотнете рефлексия была, а кода-как-данных не было.
Код-как-данные — это Expression<T>. Рефлексия — System.Runtime.Reflection.

V>Да какие проблемы (мать, мать, мать...) — опиши сам объект как неизменяемый.

V>Тебе показать, как описывать иммутабельные объекты в С++?
Ага, вижу, начинается понимание

V>Но тип аргумента-объекта уже должен уже быть? ))

Конечно.

S>>В качестве упражнения можете попробовать спроектировать на С++ пару абстрактных классов — изменяемый вектор и immutable вектор. В терминах дотнета это были бы IImmutableList<T> и IList<T>. И посмотрите, можно ли их отнаследовать друг от друга; можно ли получить один из другого при помощи модификатора const; понадобится ли вообще модификатор const при проектировании этих классов или при их использовании.


V>1. В дотнете я могу породить мутабельный тип из IImmutableList<T> аж бегом и со свистом.

Породить-то можете, а что вы с ним будете делать дальше?
Вы понимаете, что он будет нарушать спецификацию интерфейса?
V>2. В этой же технике в точности аналогично можно и в С++.
Ага. Осталось понять, при чём тут const, и устыдиться. Упражнение-то выполнить сможете?

V>Еще в прошлые разы я обращал твоё внимание на то, что более выгодно с практической точки зрения, чтобы не твой код требовал иммутабельности объекта, а чтобы внешний код требовал, чтобы твой код не мог модифицировать объект.

Пытаетесь подменить задачу. Знакомо.

V>Почему так? — по очевидной причине, ведь твой код тогда сможет работать как с тру-иммутабельными объектами, так и с неиммутабльными при рождении, но которые решили сделать иммутабельными "чуть позже". Например, некий достаточно сложно инициализируемый ветвистый словарь чего-то там.

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

V>В целях эффективности в момент наполнения словарь может быть мутабельным, а потом в какой-то момент владение передаётся константной ссылке/указателю и ву а ля.

V>В отсутствии const в этом сценарии потребуется два типа — мутальный и иммутабельный, где готовый мутабельный словарь копируется в иммутабельный, с соответствующими накладными расходами и прессингом на память.
Фантазии, фантазии. Посмотрите, как устроен ImmutableDictionary в дотнете. Там нет const, и при этом "накладные расходы и прессинг на память" ровно такие же, как в вашем сценарии.

V>В С+ в этом сценарии достаточно скопировать ссылку/указатель.

Ну нет конечно, недостаточно. С++ не даст мне гарантии, что кто-то не поменяет этот словарь после того, как я заложился на его неизменность.
Перед тем, как рассуждать об эффективности кода, нужно обеспечить его корректность.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 23.03.2023 2:21 Sinclair . Предыдущая версия .
Re[10]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 23.03.23 10:06
Оценка: -2
Здравствуйте, Sinclair, Вы писали:

V>>RTFM чего именно?

S>Истории Паскаля.

Да плевать конкретно на Паскаль, серьёзно.
Ни первый в плане P-кода, ни наиболее широко в реальных применениях его использовавший.


V>>Сам же привёл пример конкретной системы gemstone, т.е. имелась ввиду соответствующая инфраструктура в этой системе.

S>Инфраструктура построена на каких-то основах.

Я перечислял необходимые и достаточные св-ва такой системы в посте, на который ты отвечаешь (ниже по тексту).
Приводил примеры на различных технологиях — нейтивных, джава, .Net и смешанных.


S>>>Ну, так как мне воспользоваться этим кодом изнутри VB?

V>>Языками более низкого уровня.
V>>Или расписать некий фреймворк оперирования памятью в некоей библиотеке и сделать импорт функций в VB (есть такая возможность), чтобы иметь требуемый инструментарий.
V>>Например, можно взять готовую либу CRT.
S>Давайте пример кода на VB, который при помощи готовой либы CRT отправляет пользовательский предикат на сервер для исполнения.

Тут ты потерял контекст, речь шла об произвольном управлении памятью, в т.ч. многопоточной (в VB можно задать тип аппартамента для компонент).
Показать как в VB делать импорт ф-ий из DLL?
Напомнить, что в VB есть модификатор ByRef, т.е. даже можно пользовать даже такие "железячные" примитивы как InterlockedIncrement и прочие из этой оперы.

Насчёт отправить код куда-то для исполнения — примеры приводил ниже: ActiveX компоненты в браузере.
Сколько их было написано именно на VB — одному богу известно. ))
Еще на VB легко запустить удалённую прогу (COM+) и общаться с ней, делая удалённые вызовы.


V>>Потому что в объектных файлах содержится код и данные со всей сопутствующей метаинформацией.

S>Это совершенно никак не связано с возможностями по манипуляции кодом как данными изнутри языка программирования.

Очень смелое или очень странное утверждение, на выбор. ))
Вот есть открытый проект llvm, в состав проекта входит ВСЯ функциональность оперирования над кодом виртуальной машинки, включая эмулятор этой машинки и JIT.
Есть фронтенды с разных языков, включая С++.
Народ подключает это в свои проекты и балуется:
https://habr.com/ru/company/timeweb/blog/572878/

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


S>Я не понимаю, почему эта очевидная вещь до вас не доходит.


До меня не доходит, где ты видишь технические проблемы.


S>Компиляция в .obj была принята для практически 100% языков программирования; при этом возможностей мета-манипуляций в рантайме не было практически нигде.


В смысле? ))
Оперирование над obj-файлми — это свободно доступная библиотечная функциональность.

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

Лично я бы взял за основу для нейтива llvm или wasm, т.к. степень готовности к таким задачам там 100%.


V>>Если требуется посылать код другому устройству, то требуется переносимость кода.

S>В первую очередь требуется возможность этот код описать в посылаемом виде.

Опиши ф-ии, оформи их в бинарный модуль, передай модуль на противоположную сторону (это может быть и сервером, и клиентом) с идентификатором вызываемой ф-ии или без идентификатора, если есть некие соглашения об именовании "Предиката" в модуле.
Ну и некая обвязка для кеширования модулей, их идентификации и версионности.

В древних COM+/ActiveX всё это есть.

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


V>>Я уже начинаю подозревать, что ты видишь возможность переносимости кода только в виде его AST-представления.

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

Конечно, такая возможность есть.
В чём проблема скомпилять бинарный модуль под llvm или wasm на С++ или Rust?


S>Во-вторых, для систем типа GemStone возможность получить код в виде AST, а не байт-кода, является необходимостью.


Это частности.
Под JS-Нодой можно прям исходники гонять туда-сюда.
(и всеми другими динамическими языками, в которых присутствует eval или его аналог)
Под джавой, дотнетом, llvm и wasm можно гонять скомпилённые бинарники абстрактной машинки.
Во времена засилья Windows в и-нете гоняли бинарники под архитектуру i386 в виде ActiveX.


S>Потому, что предикат не исполняется напрямую. СУБД на основе предиката строит план исполнения запроса, в котором одна часть предиката будет ключом поиска в индексе, другая будет фильтром по этому индексу, а третья будет вычисляться по связанной с индексом таблице. Это вам ликбез на пальцах.


Пфф, это не ликбез, это опущенное тобой условие конкретно этой задачи. ))
Я думал речь о чём-то навроде хранимок на .Net.

Достаточно было правильно сформулировать задачу: "Требуется именно AST выражения".
Это в разы более простая задача.

На том же Форт это решается через макросы — перехватывается вход и далее слова можно "записывать" в некую свою структуру.

На плюсах аналогичные решения есть в библиотеке Boost.Proto в связке с Boost.Mpl, Boost.Fusion и прочих библиотек для метапрограммирования.
В чём суть:
— есть некие плейсхолдеры (_, _1, _2 и т.д.) которые представляют из себя "начальные" типы с переопределёнными всеми операторами.
Каждый оператор порождает очередной узел AST, у которого так же переопределены все допустимые для данного типа узла операторы.

Выглядит это примерно так:
auto avg = (_1 + _2) / 2;

assert(avg(1, 3) == 2);

bool is_equal(double x, double y) {
    return std::fabs(x - y) < std::numeric_limits<double>::epsilon();
}

assert(is_equal(avg(5.0, 6.0), 5.5));


Получившееся значение (здесь переменная avg) — это аналог expression tree в дотнете.
Выражение может состоять из вложенных вызовов подобных ф-ий.
Есть биндинг, разумеется, в том числе частичный, т.е. весь аппарат функций высших порядков.

Это выражение может исполняться через operator(), т.е. просто вызываться как ф-ия с аргументами.
Выражение можно инспектировать с целью узнать структуру.
Останется вопрос сериализации и десериализации такого AST для передачи другой стороне.


S>Предикат, записанный в виде императивного байт-кода, таким преобразованиям поддаётся плохо либо никак.


Учись спрашивать.

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

Но у тебя их нет, путей отступления.
Их нет намного раньше, чем в дотнете появились expression tree, не знал? ))

Понятно, что ты был не в курсе, т.к. за другими технологиями особо не следишь...
Мог бы просто спросить "а как получить AST выражения в С++?", потому что "отправить удалённо" — это ненужный информационный шум к такой задаче.

Boost.Proto is a framework for building Embedded Domain-Specific Languages in C++.
It provides tools for constructing, type-checking, transforming and executing expression templates.

More specifically, Proto provides:
— An expression tree data structure.
— A mechanism for giving expressions additional behaviors and members.
— Operator overloads for building the tree from an expression.
— Utilities for defining the grammar to which an expression must conform.
— An extensible mechanism for immediately executing an expression template.
— An extensible set of tree transformations to apply to expression trees.

Отредактировано 23.03.2023 11:07 vdimas . Предыдущая версия .
Re[9]: понимание ООП Алана Кея
От: Ночной Смотрящий Россия  
Дата: 23.03.23 11:19
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Сам я впервые "вживую" курочил P-код в Бейсике ZX Spectrum.


Там не совсем тот P-код. Там просто упакованноое в удобное для интерпретации представление AST было, а в описываемых Паскалях это был машинный код виртуального CPU, как сейчас в Java и дотнете.

V>Паскаль под ZX Spectrum компилял в нейтив.

V>Паскали под IBM PC ходили борландовый и от MS, в P-код не компиллировали.

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

Two early compilers generating p-code were the Pascal-P compiler in 1973, by Kesav V. Nori, Urs Ammann, Kathleen Jensen, Hans-Heinrich Nageli, and Christian Jacobi, and the Pascal-S compiler in 1975, by Niklaus Wirth.

... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[4]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 23.03.23 11:48
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Поэтому нет никакой проблемы, скажем, отправить блок кода на исполнение удалённому объекту — как это сделано в СУБД GemStone.


Минус за неточную формулировку.
Re[10]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 23.03.23 12:11
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Там не совсем тот P-код. Там просто упакованноое в удобное для интерпретации представление AST было


Там коды "инструкций" Бейсика.
Просто инструкции высокоуровневые.


НС>а в описываемых Паскалях это был машинный код виртуального CPU, как сейчас в Java и дотнете.


Немного не понимаю, почему прицепились именно к Паскалю? ))
Технология впервые была использована в другом языке.
Широко использовалась и используется тоже в других языках.

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

V>Повторюсь, любой объектный файл (или архив их в объектной библиотеке) над переносимой платформой (например, llvm) представляет из себя то же самое.
V>Даже в Паскале, Си и С++.
V>К языку это перпендикулярно.
Сколько бы раз вы ни повторяли, это не перестанет быть заблуждением.


Я не согласен, ес-но.
Взять тот же С++, в нём рефлексия предоставляется некоторыми компиляторами — clang и одним из бранчей gcc.
Так же давно есть независимые фронтенды С++, которые дают на выходе AST.
Есть портируемые бэкенды llvm и wasm, а так же все либы по интроспекции объектных файлов.
Т.е. "вклиниться" в код можно на любой стадии, от AST до бинарных кодов, вопрос будет только в настройке тулчейна, где куда и что вклинивается у целевой системы на стадии разработки.

Спекулировать тут можно только насчёт того, что в том же дотнете рефлексия, ExpressionTree и Roslyn идут в базовой поставке и являются частью стандарта, а в С++ это дополнительный инструментарий, завязанный на конкретные реализации такого инструментария. Тут бы я не спорил, но дал бы ту информацию, что работа по стандартизации рефлексии в C++ давно идёт, ожидается в стандарте С++23.
Отредактировано 23.03.2023 12:31 vdimas . Предыдущая версия . Еще …
Отредактировано 23.03.2023 12:22 vdimas . Предыдущая версия .
Отредактировано 23.03.2023 12:12 vdimas . Предыдущая версия .
Re[5]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 23.03.23 12:36
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>А некоторые концепции так и не были повторены в других языках. Например, возможность изменять и сохранять состояние системы.


Впервые реализовано в Форте. ))


Б>Или возможность изменять все элементы системы — "системные библиотеки" и т.п.


Это более распространённая функциональность — хуки.
Является неотъемлимой частью загрузчиков современных операционок.
Re[11]: понимание ООП Алана Кея
От: Ночной Смотрящий Россия  
Дата: 23.03.23 13:02
Оценка: +1
Здравствуйте, vdimas, Вы писали:

НС>>Там не совсем тот P-код. Там просто упакованноое в удобное для интерпретации представление AST было

V>Там коды "инструкций" Бейсика.

Там коды ключевых слов. Причем организовано это строго как написано. А если посмотреть код "компилятора", то там простейший парсинг и просто подмена символов на их коды. Причем оригинал не сохраняется, а восстанавливается при необходимости по этому коду. Все. На Р-код это похоже исключительно в силу того, что на Р-код похож сам исходник того бейсика. Но смысл этого — сэкономить память на хранении программы и убрать из интерпретатора парсинг, а не обеспечить переносимость.

НС>>а в описываемых Паскалях это был машинный код виртуального CPU, как сейчас в Java и дотнете.

V>Немного не понимаю, почему прицепились именно к Паскалю? ))

Я же процитировал:

Two early compilers generating p-code were the Pascal-P compiler in 1973, by Kesav V. Nori, Urs Ammann, Kathleen Jensen, Hans-Heinrich Nageli, and Christian Jacobi, and the Pascal-S compiler in 1975, by Niklaus Wirth.


V>Технология впервые была использована в другом языке.


Было что то похожее. А в полноценном виде это были именно Паскали.

V>Мой поинт был в отделении понятия "язык программирования", как совокупности ситнаксиса и семантики от способа реализации этой семантики на стадии исполнения кода


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

V>Спекулировать тут можно только насчёт того, что в том же дотнете рефлексия, ExpressionTree и Roslyn идут в базовой поставке и являются частью стандарта, а в С++ это дополнительный инструментарий,


Вот именно. Нет ABI — все, куча сценариев закрывается.
Но вы тут опять с терминами устроили кашу, уж не знаю, случайно или намеренно. Р-код это непосредственно императивные инструкции и это отдельная песня. А rtti — это другая песня, хоть и упаковываетcя она в те же файлы. Зачем пытаться это все обсуждать одновременно — непонятно.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[10]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 23.03.23 15:11
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Такие монстры как Голдберг служили неплохим противовесом этому, в общем-то, нубу-самоучке.

S>А что, Голдберг где-то заявляла, что язык Smalltalk был вдохновлён Фортом?

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

Из Форта взяли уже опробованные решения:
— шитый код, стековую машинку;
— возможность сохранения текущего состояния системы и повторной загрузки.

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

Только в 3-й версии в Smalltalk-80 добавили вменяемую интроспекцию (а это уже языку 8 лет).
Отдалённо это можно сравнить с Лиспом и его расширением CLOS, потому что на деле нифига не так — в Лиспе хранится непосредственно AST в виде кортежей { функция, аргументы }.
А в Smalltalk код не хранится в таком виде.
Он хранится примерно как в Форте.
AST можно построить только обратной декомпиляцией, собсно, как это делается в Форт словом "see", куда я тебя уже отсылал, просто ты не понял — зачем. ))
Это примерно как обратная декомпиляция байткода CLI обратно в C#, только намного проще из-за простоты организации шитого кода.

Тебе же говорилось — ты слышал звон.


V>>Какой там в опу Лисп? ))

S>Да почитайте же уже The Early History of Smalltalk и перестаньте позориться.

Ты отвечай по-существу, не юли.
То, что сам Алан ретроспективно сравнивает свои решения с Лиспом и останавливается на отличиях — оно не делает ни тепло, ни холодно.
К тому же, он не единственный автор языка и его реализатор.


V>>Похоже, ты не трогал толком ни Смолтолк, ни Лисп, ни Форт, не понимаешь внутренней механики этих языков, но пытаешься фантазировать.

S>Вы всё время путаете дизайн языков с внутренней механикой компилятора и рантайма.

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

Я хорошо понимаю принятые в процессе разработки языка (и затем системы разработки на его основе) решения, а так же, откуда они взялись, где были уже обкатаны и доказали свою полезность/эффективность.


V>>Т.е. в изначальном Лиспе никаких метаклассов не было, как и не было в Лиспе ООП, ес-но, были только ф-ии как первоклассные объекты (сам код) и данные в виде однонаправленно связанных встроенных в систему списков.

S>Конечно не было. Именно это Кей и реализовал — ему хотелось сделать систему, которая была построена на минимуме примитивов, как Лисп, но с ООП. Если бы в Лиспе было ООП, то Smalltalk бы и не понадобился.

Понадобился бы.
Смолтолк намного эффективнее Лиспа, хотя и уступал нейтивно-компиллируемым языкам.
Смолтолк выразительнее Лиспа при описании ООП.
И вообще при описании банальных арифметических и логических выражений и просто императивного кода.
Уже этих двух аргументов достаточно, чтобы не воспринимать Лисп как удовлетворительную ООП-систему, хотя с ООП там более-менее в CLOS.

Просто надо понимать, как именно тогда использовался LISP.
Он был что-то вроде современного Питона, т.е. языком, на котором было легко обкатывать концепции, экспериментировать и т.д.

Я бы еще понял, если бы ты сформулировал "Смолтолк был вдохновлён недовольством Лиспом", бо это близко к истине.
И не только Лиспом, ес-но, просто Лисп — удобный мальчик для битья. ))


V>>Курить, что есть шитый код.

S>Я знаю, что такое шитый код. Какое отношение он имеет к дизайну языка Smalltalk?

Прямое.
Во главу угла изначально ставилась простота реализации.
Шитый код давал в разы лучшую эффективность, чем машинка Лиспа, всё еще позволяя удобно манипулировать бинарным кодом, порождать его "на лету" и т.д.

Дизайн языка не был сформирован чисто теоретически.
Это была рекурсивная разработка, где особенности реализации нижнего уровня влияли на верхние и обратно тоже.
Т.е. никто не пытался разработать совсем абстрактный язык, как в случае с тем же Паскалем-Обероном или Хаскелем.
Смолтолк — это изначально "реальный" язык. ))


V>>Пример развития Форта до переносимости м/у устройствами.

S>По-прежнему не понимаю, какое отношение Постскрипт с его переносимостью имеет к ООП Алана Кея.

Это уже юление.
Ты говорил про переносимость кода тут.
Хотя на самом деле имел ввиду не это.
(или же намеренно оставил как "последний аргумент", хотя я несколько раз явно дал понять, как понял твою задачу).


V>>Таки, самообразованием принято заниматься самостоятельно, не испытывая терпение коллег.

S>Ну так займитесь. Почему вы вместо того, чтобы почитать первоисточники, занимаетесь гаданием на МК-51?

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

— Аллан не изобрел ООП (хотя позиционирует себя как автор ООП).
— В его формулировке ООП неполно: http://www.rsdn.org/forum/flame.comp/8471966.1
— Он не изобрел ни одного принципиального решения, легшего в основу Смолтолка.

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

Он, по-сути, "интегратор-компилятор" — увидел возможность реализации полудинамического ООП-языка с достаточной эффективностью на основе имеющихся уже в IT на тот момент наработок.
До Смолтолка ООП-языки были только статическими (библиотечная эмуляция ООП на Лиспе не в счёт, бо эмулировать можно что угодно на чём угодно).
Он предложил создать систему с такими св-вами, пообещав, что реализация должна получиться простой, всё.
Но работали они над языком аж 8 лет, прежде чем продукт стал коммерческим.
Т.е. не всё так просто, и тут очередная саечка Алану за шапкозакидательство.
(я вообще терпеть не могу поверхностных людей, особенно в инженерии)

Это что-то типа проекта Nemerle — ни одной новой концепции в языке не изобрели, просто попытка собрать удачные концепции на удачной платформе.
Но! Nemerle реализовали достаточно быстро в первой версии и достаточно полно, в отличие от.
И описание концепций Nemerle удивительно точное и полное.

Я не могу на полном серьёзе сравнивать болтовню Алана уровня болтовни бабушек у подъезда с подачей материала другими авторами.
Есть еще один такой же прихлебатель от IT — Мартин Фаулер, с Аланом два сапога пара.
Якающие нубы, за которых сплошной испанский стыд. ))


S>>>Попробуйте реализовать на Паскале, Си, и С++ функцию, которая бы передавала пользовательский предикат на сервер для исполнения.

V>>Не вижу технических проблем передать чистую ф-ию или чистое замыкание при наличии соотв. инфраструктуры.
S>Пример кода в студию. Хотя бы на одном из языков.

В предыдущем сообщении пример дан.


V>>Напомню, что MS компилятор С++ умеет компиллировать unamanaged C++ код (т.е. безо-всяких managed расширений) в чистый байт-код.

V>>(тип генерируемого образа для режима С++/CLI задаётся опциями компилятора)
S>Ну ок, если вам будет проще — попробуйте изобразить требуемую функциональность на unmanaged C++, скомпилированном в чистый байт-код.

Точную форулировку требуемой функциональности в студию.


V>>Ну вот попробуй на Лисп (или другом динамическом языке) без динамического исполнения текстовых исходников, а я поржу.

S> Посмотрите на то, как устроены макросы Лиспа. Там нет ничего про динамическое исполнение текстовых исходников.
S>Всё построено на манипуляциях кодом в виде AST. Можете начинать ржать.

Это залёт, курсант! ))
Код макросов лиспа не манипулирует AST явно.
Макросы Лиспа подстановочные (шаблонные), как макросы Си/С++ или макроассемблера.
Заметное отличие лишь в том, что макросы Лиспа МОГУТ быть гигиеническими (если программист не забудет экранировать область видимости символов, бгг).

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


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

V>>Похоже, у тебя каша в голове из св-в языков и конкретных их компиляторов/интерпретаторов.
S>Всё ровно наоборот — это у вас каша.

У меня понимание, как оно устроено и работает.
А у тебя бесконечные "слышал звон" и передёргивания из разряда сказала-мазала.


V>>Язык, по-сути, тот же.

V>>Отличается представлением скомпиллированного кода, а значит, особенностями модели вычисления.
S>И самое замечательное, что эти особенности модели вычисления никак не влияют на то, о чём я говорю.

Еще как влияют.
Макросы Схемы живут только в момент компиляции, в отличии от макросов Лиспа, которые становятся частью текущего состояния "системы".
Макросы Схемы имеют другое ключевое слово и пару особенностей.
Но это по-прежнему подстановочные/позиционные макросы, без явного манипулирования AST.


S>Макросы в Схеме всё ещё есть, и это означает, что я могу решить обозначенную задачу и на схеме тоже.


Увы, увы.

Причём, тут я противоречу самому себе, вроде бы, ведь можно представить себе такую реализацию Схемы, которая ведёт себя как Лисп?
Представить такую реализацию можно, да вот только Схема — это именно компиллируемая версия Лиспа. ))
А динамическая версия — это сам Лисп.

Для целей эффективного конечного кода пришлось внести в Лисп несколько ограничений, наверно поэтому назвали другим языком и стандарты Схемы с тех пор развиваются независимо от Лиспа.
Вернее, впервые стандарты на семейство Лисп появились в Схеме — именно из-за вносимых ограничений.
Заметно позже сообщество стало формировать стандарты и на Лисп — CL.
Ну и, стандарты на эмуляцию ООП в Лиспе поверх CL — CLOS.

Хотя именно с точки зрения языка (а не подробностей его реализации) Схему можно считать диалектом Лиспа, конечно!


S>А всё потому, что решают тут свойства языка. Что там под капотом — дело десятое.


Кошмар
У языка два свойства — синтаксис и семантика.
Если тебе надо получить AST — это всего лишь синтаксис.
Никакой язык не запрещает получать синтаксис своих выражений.
И отправить на сервер ты хотел не код на исполнение, как выяснилось, а AST выражений на трансформацию.

Для исполнения отправляют более эффективный в исполнении код, обычно, поэтому я и показывал, как это делают в других технологиях.
Не берём пример браузеров и JS, бо это совсем кошмар — тянуть исходники кода на исполнение.


V>>Не осенило еще? ))

S>Вижу, что вас не осенило. Причина, я думаю, именно в том, что вы не пробуете сделать то, о чём я говорю.

Сделать что именно?
Учись спрашивать.


V>>Например, MS Visual Basic for DOS компиллировал в неплохо оптимизированный код (я курочил дизассемблером что он там порождает — да там офигенно, на уровне их же компилятора Си и Паскаля).

S>Да видел конечно. Толку-то? Ни на одном из этих бейсиков я не могу отправить на сервер код пользовательского предиката.

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


V>>В Лисп точно так же может сосуществовать сколько угодно много независимых ООП-подсистем библиотечного уровня.

S>Я в курсе. Синтаксис Лиспа настолько ужасен, что его ничем испортить нельзя. Поэтому всякие надстройки над ним выглядят не хуже оригинального лиспа.
S>И можно делать любое ООП, которое нравится — с наследованием интерфейсов и наследованием реализации, со множественным наследованием и без, с любыми наборами модификаторов "видимости" мемберов. Можно придумывать свойства и события; можно делать наследование экземпляров как в JS; можно обрабатывать неизвестные сообщения, как в Smalltalk.
S>Правда, всё это будет всё в том же ужасном стиле скобок поверх скобок. Но на фоне остального лисп-кода выделяться не будет.

В любом случае, это лишь эмуляция.
А эмулировать что угодно можно на любом Тьюринг-полном языке.
Объекты в Лиспе не являются первоклассными сущностями языка, вот в чём косяк.
Первоклассной сущностью там являются числа/символы, функции и пара CONS(car, cdr), где каждый элемент пары может быть числом, функцией или опять парой (ссылочная семантика для всего перечисленного).


S>В общем, Лисп считать ОО-языком бессмысленно.


Именно.
Зато Форт (бгг, сорри... Но Чарльз Мур был, таки, умнейшим челом) позволяет сделать первоклассной сущностью любую хрень.
Форт рассматривается опытным фортером как язык для порождения языков (из-за особенностей макросистемы, которая нифига не как в Лиспе), и именно так пишутся на ём программы — исключительно через определение слоёв DSL от самых низких до целевых. Не зря конструкции Форта называются словами. Программа в форте — это всегда описание новых слов. А с помощью них — других, более ёмких.

ничего более элегантного я не видел до сих пор, т.к. виденные другие языки навязывают синтаксис и философию.
Форт навязывает лишь способ мышления, не навязывая остального.
И при этом чудовищно прост в реализации.
И при этом чудовищно эффективен, в сравнении с Лиспом.
Порой и в сравнении с нейтивом из-за особенностей манипулирования стеком — в Форте обычно меньше паразитных манипуляций данными в стеках/регистрах, меньше ненужных копирований/преобразований данных — состояние стека на выходе одного слова часто является готовым состоянием стека для вызова другого. Писать программы на Форте — это как играть в Тетрис, натурально, все фигуры должны стыковаться, и тогда прога просто летает. ))


V>>Ты не понимаешь мотива принятия некоторых важных решений в IT в ретроспективе.

S>

Более того, ты зачем-то отмахиваешься от такой инфрмации, оперируя вещами, скорее, эмоциональными, чем рациональными.


V>>Проводимый ликбез тебя коробит, но это особенности твоего чванства и ничего более.

S>Скорее, это особенности вашего чванства.

За новую инфу я всегда благодарен, в отличие от.
А тебя аналогичный сценарий всегда бесит, что ты аж перестаёшь быть корректным в логике спора. ))
Отредактировано 23.03.2023 21:43 vdimas . Предыдущая версия . Еще …
Отредактировано 23.03.2023 21:35 vdimas . Предыдущая версия .
Отредактировано 23.03.2023 15:34 vdimas . Предыдущая версия .
Отредактировано 23.03.2023 15:26 vdimas . Предыдущая версия .
Отредактировано 23.03.2023 15:23 vdimas . Предыдущая версия .
Отредактировано 23.03.2023 15:21 vdimas . Предыдущая версия .
Отредактировано 23.03.2023 15:14 vdimas . Предыдущая версия .
Re[10]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 24.03.23 09:42
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

V>>Мы тут говорили о коде как о данных.

V>>Это появилось только с метаклассами в Смолтолке-80.
V>>До этого никакой рефлексии не было.
S>Рефлексия ортогональна коду-как-данным.

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


S>В лиспе есть код-как-данные, но рефлексии может и не быть.


Это ничем не будет отличаться от указателя на функцию в С++ с т.з. программиста.


S>В раннем дотнете рефлексия была, а кода-как-данных не было.


И как же Reflector работал? ))
А всякие AOP-ориентированные расширения?
При наличии взаимно-однозначного отображения кода на AST такие рассуждения сходу превращаются в спекуляции.


S>Код-как-данные — это Expression<T>.


Expression<T> — это "AST на блюдечке" и не более чем.
Просто высокоуровневое представление кода в виде графа, чего в общем случае не требуется, в общем-то, потому что никто не работает со всем графом "одновременно" — его обходят визиторами.
Выглядит так, что ты не понимал, при чём тут стековая машинка в течении всего этого обсуждения, блин, т.е. всё еще хуже, чем я думал.

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


Это как если запустить над графом итератор-визитор, код стековой машинки будет такой:
A C E D B H I G F
(вычисляем A, кладём на стек; вычисляем C, кладём на стек, вычисляем E, далее результаты вычислений С и E заменяются на стеке результатом вычисления D и т.д.)

Результат обхода дерева и представление кода в виде дерева(графа) взаимно однозначны.
Сответственно, трансформацию кода можно выполнять прямо по последовательности обхода, а не по графу — бо оно так в реальной жизни и происходит — через итерирование дерева.
Например, на стадии бета-редукции известные C и E могут быть заменены предвычисленным D', тогда тройка C E D в последовательности кода заменяется просто на D'.

Вот так ты грохнулся фейсом в лужу — нет в Смолтолке никаких аналогов Expression<T>, а код как данные есть.
"Слышал звон" во всей своей красе, бгг...
Молодец, чо! Зато спорим до усрачки!


S>Рефлексия — System.Runtime.Reflection.


Да понятно, что у тебя на дотнете свет клином сошелся, хосподя.


V>>Плюс аргументами генериков не могут быть ref-структуры, поэтому, приходится описывать типы делегатов ручками как в старые добрые времена, когда генериков еще не было, бгг...

S>Это всё не относится к вопросам ООП парадигмы, а скорее о проектировании реальной платформы в условиях противоречивых ограничений.

Эти ограничения не с потолка взялись, а являются следствием недостаточной продуманости функционального типа.
Например, в Смолтолке этой ошибки не совершили.
Нет абсолютно никаких проблем в описании полноценного функционального типа в дотнете.
Напомню, что в дотнете есть не только юзверские атрибуты, наследником от Attribute, но и "системные", то бишь просто битовые флаги.
Свободные биты были, верификатор мог быть удовлетворён.
А сейчас "проклятое легаси" гирей на ногах, а как же!


V>>Да какие проблемы (мать, мать, мать...) — опиши сам объект как неизменяемый.

V>>Тебе показать, как описывать иммутабельные объекты в С++?
S>Ага, вижу, начинается понимание

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


V>>Но тип аргумента-объекта уже должен уже быть?

S>Конечно.

Но в рассуждениях ты об этом "забыл".


V>>1. В дотнете я могу породить мутабельный тип из IImmutableList<T> аж бегом и со свистом.

S>Породить-то можете, а что вы с ним будете делать дальше?

Подавать туда, где ожидается IImmutableList<T> и нарушать тем самым контракт иммутабельности, заложенный при проектировании.
Хулиганить, одним словом, показывая как ущербность твоих аргументов, так и отсутствие гарантий в такой системе.


S>Вы понимаете, что он будет нарушать спецификацию интерфейса?


Дошло, наконец.
А кто мне запретит нарушать?
Где эти пресловутые гарантии?
Почему я вообще должен объяснять взрослому инженеру банальности на пальцах?

Гарантии иммутабельности может иметь только сам тип.



V>>2. В этой же технике в точности аналогично можно и в С++.

S>Ага. Осталось понять, при чём тут const, и устыдиться.

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


S>Упражнение-то выполнить сможете?


У тебя как обычно проблема с формулировкой условий задач.
Из предыдущего твоего поста звучит так, что модификатор const должен волшебным образом привести интерфейс IList<T> к IImmutableList<T>.
Это уже не просто не смешно, это проблемки с мыслительным аппаратом.


V>>Еще в прошлые разы я обращал твоё внимание на то, что более выгодно с практической точки зрения, чтобы не твой код требовал иммутабельности объекта, а чтобы внешний код требовал, чтобы твой код не мог модифицировать объект.

S>Пытаетесь подменить задачу.

Пытаюсь достучаться до непроходимого коллеги — иммутабельность заключена в описании типа, а не в коде, обрабатывающего тип.
Код всего лишь работает в условиях ограничений.
Это не подмена задачи, это обычная принятая в IT практика, которая проистекает из основ логики — необходимость не равна достаточности.
Любое ограничение — это введение признака "необходимости".
Не обязательно ограничение const, есть еще другие модификаторы, если речь про С++.


V>>Почему так? — по очевидной причине, ведь твой код тогда сможет работать как с тру-иммутабельными объектами, так и с неиммутабльными при рождении, но которые решили сделать иммутабельными "чуть позже". Например, некий достаточно сложно инициализируемый ветвистый словарь чего-то там.

S>Ну так для того, чтобы "мой" код корректно работал, мне нужны гарантии не только того, что я не изменяю объект, но и что никто другой его тоже не изменяет.

Опиши имутабельный тип, какие проблемы?
Это ж твой код? Ну и вот. ))
У тебя ведь нет никаких гарантий относительно IImmutableList<T>, но спать спокойно тебе это не мешает.
Забавно порой общаться со столь ангажированными личностями, однако.


S>Посмотрите, как устроен ImmutableDictionary в дотнете. Там нет const, и при этом "накладные расходы и прессинг на память" ровно такие же, как в вашем сценарии.


Бгг, ну так и сравни кол-во выделений из кучи для мильона вхождений в случае обычного словаря и иммутабельного.
Разница в мильон раз.


V>>В С+ в этом сценарии достаточно скопировать ссылку/указатель.

S>Ну нет конечно, недостаточно.

Достаточно.


S>С++ не даст мне гарантии, что кто-то не поменяет этот словарь после того, как я заложился на его неизменность.


Не кто-то, а ты, как программист.
Пишешь функцию инициализации такого словаря, возвращаешь из неё const ссылку/указатель на этот словарь.
"Внешний" код будет обладать только константной ссылкой (которую нельзя скопировать в неконстантную), гарантии соблюдены, ву а ля.
То бишь, со своей стороны кода ты достаточностью автоматом покрыл необходимость.
Л — логика!


S>Перед тем, как рассуждать об эффективности кода, нужно обеспечить его корректность.


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

Скажем так, тебе долго удавалось водить коллег за нос, выдавая себя за хорошо соображающего.
Прошли годы, ты стал смелее, "раскрылся", сегодня я сразу предположил, что ты банально не втыкаешь, путаешь два важных, но разных кейза (пусть даже второй кейз покрывается первым, но он не теряет важности и в случае самостоятельного использования).
И я оказался прав!
Ты действительно не отличаешь одно от другого.
Да и вообще, плохо понимаешь, как распространяются гарантии.
Пришлось напоминать, что если гарантии нужны железобетонные, то они и должны идти с самого начала — с объявления типов (полей таких типов).
Код уже работает над объявлеными типами (и полями, если это методы).

Не зря этот подход был скопирован из С++ в C# для структур.
А для классов по прежнему глухо — IImmutableList<T> не обеспечивает ни необходимости, ни достаточности.

Поэтому твои вопросы насчёт "где мне взять уверенность" могли бы относиться к дотнету, но никак ни к С++, бо там эту уверенность при надобности обеспечить можно — все средства для этого есть, велкам!
А в дотнете — дудки!
Отредактировано 27.03.2023 11:31 vdimas . Предыдущая версия . Еще …
Отредактировано 24.03.2023 11:42 vdimas . Предыдущая версия .
Отредактировано 24.03.2023 9:52 vdimas . Предыдущая версия .
Отредактировано 24.03.2023 9:51 vdimas . Предыдущая версия .
Отредактировано 24.03.2023 9:50 vdimas . Предыдущая версия .
Отредактировано 24.03.2023 9:48 vdimas . Предыдущая версия .
Отредактировано 24.03.2023 9:46 vdimas . Предыдущая версия .
Отредактировано 24.03.2023 9:43 vdimas . Предыдущая версия .
Re[11]: понимание ООП Алана Кея
От: Ночной Смотрящий Россия  
Дата: 24.03.23 11:44
Оценка: +1
Здравствуйте, vdimas, Вы писали:

S>>В раннем дотнете рефлексия была, а кода-как-данных не было.

V>И как же Reflector работал? ))

Фигово. Делать на основе этого решения, не связанные с реверс-инжинирингом было невозможно.

V>А всякие AOP-ориентированные расширения?


А это вообще не про код как данные.

V>При наличии взаимно-однозначного отображения кода на AST


Не было такого. Очень похоже — да, но 100% точности не было.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[12]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 24.03.23 12:02
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

S>>>В раннем дотнете рефлексия была, а кода-как-данных не было.

V>>И как же Reflector работал? ))
НС>Фигово. Делать на основе этого решения, не связанные с реверс-инжинирингом было невозможно.

Фиговость проистекает из очевидного — CLI-машинка разрабатывалась не как привязанная конкретно к языку C#.
Если бы привязанность была, было бы меньше допущений, меньше инструкций (часть из них были бы более высокоуровневые), существовало бы однозначное отображение байт-кода на C#.
А так-то, разумеется, можно породить такой байт-код, который не маппится на валидный C#.
Это был просто пример, который показывает суть — не всегда требуется хранить граф AST в явном виде.


V>>А всякие AOP-ориентированные расширения?

НС>А это вообще не про код как данные.

Однако, они работали уже над бинарным кодом когда-то до Рослина. ))


V>>При наличии взаимно-однозначного отображения кода на AST

НС>Не было такого. Очень похоже — да, но 100% точности не было.

Мы тут о Смолтолке рассуждали и вообще о стековых машинках.
Просто стековые машинки дотнета и джавы не являются функционально-чистыми стековыми машинками — в них выделение ячеек стека может происходить без инициализации ячеек, то бишь, в стек кладутся не результаты вычислений, а просто может быть зарезервировано место. Такие операции относятся к "служебным", явно в код не отображаются, могли бы обыгрываться уже на уровне JIT... Просто тогда JIT вынужден был бы отжирать больше ресурсов, в общем, сплошные компромисс на компромиссе. ))

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

И еще момент, походу, Синклер упустил важное — для передачи графа-AST "куда-то" (именно графа) его надо сериализовать и затем десериализовать.

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

Т.е., в случае стековой машинки трасформацию графа AST можно выполнять прямо над сериализованным представлением.
Собсно, именно так и работает бета-редукция в функциональных языках.
Отредактировано 24.03.2023 14:14 vdimas . Предыдущая версия . Еще …
Отредактировано 24.03.2023 12:03 vdimas . Предыдущая версия .
Re[13]: понимание ООП Алана Кея
От: Ночной Смотрящий Россия  
Дата: 24.03.23 12:10
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Фиговость проистекает из очевидного — CLI-машинка разрабатывалась не как привязанная конкретно к языку C#.


Что это меняет в контексте разговора?

V>Если бы привязанность была,


Если бы у бабушки ...

V>Это был просто пример, который показывает суть — не всегда требуется хранить граф AST в явном виде.


Пример чего? Тебе вроде бы сказали простую вещь — либо в платформе код как данные это first class citizen, либо нет. Никакой рефлектор на это не влияет, и пример твой какой то нерелевантный совсем.

V>>>А всякие AOP-ориентированные расширения?

НС>>А это вообще не про код как данные.
V>Однако, они работали уже над бинарным кодом когда-то до Рослина. ))

См. выше.

Бла-бла-бла не по теме скипнуто.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[10]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 24.03.23 12:34
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Ну так для того, чтобы "мой" код корректно работал, мне нужны гарантии не только того, что я не изменяю объект, но и что никто другой его тоже не изменяет.


Вдогонку, паттер Wrapper (Decorator) никто не отменял.
Оберни нужный тип по-значению (абстракции в С++ бесплатны, угу), опиши оборачиваемое значение как константное поле типа (т.е. инициализируемое только в конструкторе), опиши соотв. делегирующие const-методы.
Конструктор объяви перемещающим, чтобы гарантировать отсутствие немутабельных внешних ссылок на владеемые тобой данные.
Сделать это можно однократно в несколько строк в шаблонном виде, что для типа-ключа, что для словаря целиком.
Такая система ограничений будет работать с любым объектом, удовлетворяющим публичному интерфейсу std::map (их даже изкаробки идёт уже несколько).
Свой шаблонный код опиши уже в концептах и будет тебе железобетонная уверенность. ))

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

Если тебе интересно закрыть эту тему раз и навсегда — распишу весь код целиком.
Отредактировано 24.03.2023 13:46 vdimas . Предыдущая версия .
Re[14]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 24.03.23 12:50
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

V>>Фиговость проистекает из очевидного — CLI-машинка разрабатывалась не как привязанная конкретно к языку C#.

НС>Что это меняет в контексте разговора?

Ничего.
Твоё замечание о конкретике было верным, но мой пример как более общий тоже неплохо работает.


V>>Если бы привязанность была,

НС>Если бы у бабушки ...

Конкретика, конкретика...
Я отсылал коллегу к хорошо ему знакомому.
Мне этот пример не требовался.


V>>Это был просто пример, который показывает суть — не всегда требуется хранить граф AST в явном виде.

НС>Пример чего? Тебе вроде бы сказали простую вещь — либо в платформе код как данные это first class citizen, либо нет.

Expression<T> и его наследники не являются first class citizen.
С т.з. языка это "пользовательские типы данных".
В общем, требование first class citizen надуманное.
Рассуждать можно лишь о наличии такой реализации или нет.
Или о наличии нескольких реализаций.
Тебе никто не запретит расписать свою независимую иерархию-аналог, вклинившись через Рослин в компиляцию.

Вдогонку, first class citizen имеют неизвестную с т.з. языка структуру.
В том же Лиспе можно получить тело ф-ии в виде списка, но нельзя понять, как эта ф-ия на самом деле устроена унутре на уровне машинки.
А там не только тело, это специальная системная запись-символ.
Структура символа неизвестна.

Как пример — строки или делегаты в дотнете.
Имеют неизвестную с т.з. языка внутреннюю структуру.


НС>Бла-бла-бла не по теме скипнуто.


Смотря какая тема.
Если тема конкретно байт-кода дотнета — то в контексте обсуждения не принципиально.
При завязке только на C#, байт-код дотнета мог бы состоять только из помещения значений и литералов на стек и вызовов ф-ий (методов).
Это было бы точное сериализованное представление AST, взаимно однозначное с его представлением в виде графа.

Зацепились-то за "явное представление" AST.
Пахнуло дотнетом, угу. ))

А на деле, например, при компиляции функциональных языков граф вычислений никогда не живёт в памяти целиком, просто потому что этого не требуется.
Я не раз уже обращал внимание твоё, Влада2 и Вольфхаунда, что в промышленных решениях чаще используют оперативную обработку графов, вместо сливания в бутылочное горлышко и разливание затем обратно. Даже в случае ленивых языков, цепочка бета-редукции работает именно над стековой машинкой и её итерирование происходит как показал картинкой последовательность обхода графа — т.е. в сериализованном виде. При оперативной обработке граф живёт только в голове разработчика, никогда не занимая память на все свои развесистые связи целиком, где стоимость хранения связей порой выше стоимости хранения целевых данных. ))
Отредактировано 24.03.2023 13:02 vdimas . Предыдущая версия . Еще …
Отредактировано 24.03.2023 12:58 vdimas . Предыдущая версия .
Re[15]: понимание ООП Алана Кея
От: Ночной Смотрящий Россия  
Дата: 24.03.23 17:47
Оценка: :)
Здравствуйте, vdimas, Вы писали:

НС>>Пример чего? Тебе вроде бы сказали простую вещь — либо в платформе код как данные это first class citizen, либо нет.

V>Expression<T> и его наследники не являются first class citizen.

В языке C# — являются.

V>С т.з. языка это "пользовательские типы данных".


Ты сейчас глупость написал.

V>Вдогонку, first class citizen имеют неизвестную с т.з. языка структуру.


И что? Они имеют известные с точки зрения языка название типа и имена и сигнатуры его статических методов. Это намертво прошито в компилятор. И именно проэтому это first class citizen.

Бла-бла-бла не по теме скипнуто.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[16]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 25.03.23 02:36
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>>>Пример чего? Тебе вроде бы сказали простую вещь — либо в платформе код как данные это first class citizen, либо нет.

V>>Expression<T> и его наследники не являются first class citizen.
НС>В языке C# — являются.

Только в том смысле, что наследуются от Object, который first class citizen.
Так можно про любой GC-тип дотнета сказать. ))


V>>С т.з. языка это "пользовательские типы данных".

НС>Ты сейчас глупость написал.

Ты сейчас фейсом об тейбл.

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

Граф Expression<T> прекрасно можно построить и ручками.


V>>Вдогонку, first class citizen имеют неизвестную с т.з. языка структуру.

НС>И что?

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


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

НС>И именно проэтому это first class citizen.

Сова треснула. ))
Сия агрессивная упёртость и глупый спор с базовыми вещами, которые тебе преподавали еще в ВУЗ-е, попахивают попыткой оправдать обосратушки со сроками выхода Рослина, что пришлось намертво вшивать в язык конверсию AST в Expression<T> для целей скорейшего выпуска EF и LINQ без Roslyn когда-то.

Вот я определил свой тип MyExpression, открыл в инете примеры по Рослин и за 10 минут борьбы с незнакомой предметной областью получил примерно в таком генераторе:
[Generator]
internal class MyExpressionGenerator : ISourceGenerator { 
    public void Execute(GeneratorExecutionContext context) {
        ...
        var lambdasToRewrite = tree.GetRoot()
                .DescendantNodes()
                .OfType<LambdaExpressionSyntax>()
                .Where(l => model.GetTypeInfo(l).ConvertedType?.Name == "MyExpression")
                .ToList();
        ...

список AST-узлов всех лямбд, из которых пытался получить MyExpression.
Далее дело техники — конвертировать подробное AST Рослина в упрощенное AST моего MyExpression.

Так шта, ребят, не надо ссать нам в уши, как грится...

В дотнете и C# слишком много косяков, которые случились "потому что так вышло" (С), пополняя груз "проклятого легаси". ))
Делать из этого какие-то выводы или, упаси боже, ссылаться на косяки как на аргументы — дело заведомо дохлое.

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

Expression<T> — такой же точно бред, как нетипизированная стандартная библиотека первых версий дотнета.

Потому что надо давать возможность трансформации любого кода через полноценное AST исходника, и тогда это можно рассматривать как эдакую навороченную макросистему+кодогенерацию в языке, как оно есть сейчас, а не лепить убогий костыль на некий выделенный сценарий...
Хотя, когда происходит ситуация "бизнес требует, а мы не успеваем", чего только может ни случиться...
Отредактировано 25.03.2023 2:49 vdimas . Предыдущая версия . Еще …
Отредактировано 25.03.2023 2:48 vdimas . Предыдущая версия .
Отредактировано 25.03.2023 2:47 vdimas . Предыдущая версия .
Отредактировано 25.03.2023 2:38 vdimas . Предыдущая версия .
Отредактировано 25.03.2023 2:38 vdimas . Предыдущая версия .
Re[17]: понимание ООП Алана Кея
От: Ночной Смотрящий Россия  
Дата: 25.03.23 13:21
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Граф Expression<T> прекрасно можно построить и ручками.


Ручками — можно, а чтобы компилятор за тебя собрал — нельзя.

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

НС>>И именно проэтому это first class citizen.
V>Сова треснула. ))
V>Сия агрессивная упёртость

Ну ты и чудак на букву М. Пытаешься с тобой общаться как с человеком, а ты как подзаборная пьянь отвечаешь. Адьос.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[18]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 26.03.23 15:35
Оценка: -2
Здравствуйте, Ночной Смотрящий, Вы писали:

V>>Граф Expression<T> прекрасно можно построить и ручками.

НС>Ручками — можно,

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


НС>а чтобы компилятор за тебя собрал — нельзя.


Раньше было нельзя, до Roslyn.
Сейчас можно.


V>>Сия агрессивная упёртость

НС>Ну ты и чудак на букву М. Пытаешься с тобой общаться как с человеком, а ты как подзаборная пьянь отвечаешь. Адьос.

Чьяб корова мычала со своим хамством:

V>>С т.з. языка это "пользовательские типы данных".
НС>Ты сейчас глупость написал.

Я помню, на какой специальности ты учился, значит определения знаешь.
Моё утверждение — это даже не утверждение, а напоминание определений из самой базы ЯВУ.

Да и не в этом дело — я тебе показал как работать со своим произвольным MyExpresison, т.е. порождать своё AST из лямбд через Roslyn, что множит твою агрессивную упёртость на ноль.
Отредактировано 26.03.2023 15:42 vdimas . Предыдущая версия . Еще …
Отредактировано 26.03.2023 15:38 vdimas . Предыдущая версия .
Re[11]: понимание ООП Алана Кея
От: korvin_  
Дата: 26.03.23 20:19
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Код макросов лиспа не манипулирует AST явно.

V>Макросы Лиспа подстановочные (шаблонные), как макросы Си/С++ или макроассемблера.

С чего это вдруг?

V>А вот макросы Форта устроены иначе — они получают на входе поток лексем и манипулируют с ним именно явно, порождая то самое AST.


Макросы Лиспа получают на вход дерево объектов и манипулируют с ним явно. В чём отличие?

V>Макросы Схемы живут только в момент компиляции, в отличии от макросов Лиспа


Что мешает в Схеме вызвать eval в рантайме?

V>Макросы Схемы имеют другое ключевое слово


Это существенное отличие, да.

V>и пару особенностей.


Каких?

V>Но это по-прежнему подстановочные/позиционные макросы, без явного манипулирования AST.


Что мешает работать с syntax object в Схеме через syntax-case, например?

V>Причём, тут я противоречу самому себе, вроде бы, ведь можно представить себе такую реализацию Схемы, которая ведёт себя как Лисп?

V>Представить такую реализацию можно, да вот только Схема — это именно компиллируемая версия Лиспа. ))
V>А динамическая версия — это сам Лисп.

SBCL прекрасно компилирует Common Lisp. А eval из Схемы никуда не делся.

V>Для целей эффективного конечного кода пришлось внести в Лисп несколько ограничений, наверно поэтому назвали другим языком и стандарты Схемы с тех пор развиваются независимо от Лиспа.


Схема не так и не для этого разрабатывалась.

V>Вернее, впервые стандарты на семейство Лисп появились в Схеме — именно из-за вносимых ограничений.


С чего ты взял, что из-за них?

V>В любом случае, это лишь эмуляция.


Чем здесь эмуляция отличается от «не-эмуляции»?

V>Объекты в Лиспе не являются первоклассными сущностями языка, вот в чём косяк.


Являются.

V>Первоклассной сущностью там являются числа/символы, функции и пара CONS(car, cdr), где каждый элемент пары может быть числом, функцией или опять парой (ссылочная семантика для всего перечисленного).


И много что ещё. Перечитай определение, что такое first-class citizen.

V>Форт навязывает лишь способ мышления, не навязывая остального.

V>И при этом чудовищно прост в реализации.
V>И при этом чудовищно эффективен, в сравнении с Лиспом.
V>Порой и в сравнении с нейтивом из-за особенностей манипулирования стеком — в Форте обычно меньше паразитных манипуляций данными в стеках/регистрах, меньше ненужных копирований/преобразований данных — состояние стека на выходе одного слова часто является готовым состоянием стека для вызова другого. Писать программы на Форте — это как играть в Тетрис, натурально, все фигуры должны стыковаться, и тогда прога просто летает. ))

А уж ассемблер-то как эффективен. И что из этого вытекает? Зачем все эти Лиспы, Си, Паскали и прочие Форты понапридумывали?
Re[15]: понимание ООП Алана Кея
От: korvin_  
Дата: 26.03.23 20:25
Оценка: +2
Здравствуйте, vdimas, Вы писали:

V>Expression<T> и его наследники не являются first class citizen.

V>С т.з. языка это "пользовательские типы данных".
V>В общем, требование first class citizen надуманное.

First-class citizen и built-in — ортогональные вещи.

First-class citizen — это:

Robin Popplestone gave the following definition: All items have certain fundamental rights.

All items can be the actual parameters of functions
All items can be returned as results of functions
All items can be the subject of assignment statements
All items can be tested for equality.

последний пункт неоднозначный, но «пользовательские/встроенные типы данных» тут совершенно не при чём.
Re[2]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 26.03.23 22:07
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>В его мире, к примеру, нет "скаляров" или "плоских данных". Чтобы прибавить к двойке пятёрку нужно отправить объекту "2" сообщение "прибавить" с аргументом "5", где "5" — это ссылка на объект-пятёрку.

S>В ответ на это сообщение двойка пришлёт ссылку на другой объект типа "число". И значением этого числа будет "7".

И что мы будем делать в процессе ожидания ответа на сообщение?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[3]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 27.03.23 01:02
Оценка:
Здравствуйте, T4r4sB, Вы писали:
TB>И что мы будем делать в процессе ожидания ответа на сообщение?
То же, что и в ожидании возврата из функции Add(2, 5).
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 27.03.23 08:41
Оценка:
Здравствуйте, korvin_, Вы писали:

V>>Макросы Лиспа подстановочные (шаблонные), как макросы Си/С++ или макроассемблера.

_>С чего это вдруг?

Такой дизайн. ))

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


V>>А вот макросы Форта устроены иначе — они получают на входе поток лексем и манипулируют с ним именно явно, порождая то самое AST.

_>Макросы Лиспа получают на вход дерево объектов и манипулируют с ним явно.

Разве?

(defmacro foo2 (x)
(list 'exp x))


В момент компиляции во внутренее представления встречающихся foo2 с аргументом x, де-факто будет происходить подстановка (list 'exp x) с указанным x.
Это обычный подстановочный макрос, который может быть реализован как текстуальный/шаблонный.
На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.

Никакого явного оперирования AST тут нет, как нет оперирования AST в шаблонах С++.


V>>Макросы Схемы живут только в момент компиляции, в отличии от макросов Лиспа

_>Что мешает в Схеме вызвать eval в рантайме?

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

Соответственно, у подключаемого движка-интерпретатора нет никакой связи с твоей программой.
В Схеме eval имеет сигнатуру (eval expression environment) где environment необходимо набить ручками перед вызовом eval.
Сравнить с Лиспом, в котом eval имеет сигнатуру (eval expression), а environment используется неявным образом текущий.


V>>Макросы Схемы имеют другое ключевое слово

_>Это существенное отличие, да.

Это из-за дополнительных ключевых слов в телах макросов.
Т.е. чуть другой стандарт, поэтому взяли другой идентификатор, ИМХО.


V>>и пару особенностей.

_>Каких?

Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.
Там очевидно.


V>>Но это по-прежнему подстановочные/позиционные макросы, без явного манипулирования AST.

_>Что мешает работать с syntax object в Схеме через syntax-case, например?

Это что-то вроде частичного определения шаблонов в С++ — описание неких частных случаев.
Механика та же, что и для общего случая.


V>>Причём, тут я противоречу самому себе, вроде бы, ведь можно представить себе такую реализацию Схемы, которая ведёт себя как Лисп?

V>>Представить такую реализацию можно, да вот только Схема — это именно компиллируемая версия Лиспа. ))
V>>А динамическая версия — это сам Лисп.
_>SBCL прекрасно компилирует Common Lisp.

Да пофик, динамичность никуда не девается.
Всё-равно под капотом работает Лисп-машинка.


_>А eval из Схемы никуда не делся.


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

Соответственно, если широко используются ср-ва навроде eval (primitive-eval и т.д.), то Схема теряет заложенное в ней преимущество.
Профанация получается.


V>>Для целей эффективного конечного кода пришлось внести в Лисп несколько ограничений, наверно поэтому назвали другим языком и стандарты Схемы с тех пор развиваются независимо от Лиспа.

_>Схема не так и не для этого разрабатывалась.

Для этого. ))

Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных

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

Но при надобности не стирать символы — вот вам рядышком прежний динамический eval.


V>>Вернее, впервые стандарты на семейство Лисп появились в Схеме — именно из-за вносимых ограничений.

_>С чего ты взял, что из-за них?

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


V>>В любом случае, это лишь эмуляция.

_>Чем здесь эмуляция отличается от «не-эмуляции»?

Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризации по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.

При этом теряются гарантии целостности, в сравнении с С++, т.е. в эмуляции есть возможность описать объект, имеющий некорректный "бинарный лейаут", на котором эмуляция споткнётся в рантайм (в зависимости от реализации такой эмуляции может быть проход по памяти, либо же динамически выкинута ошибка/сигнал). В ООП-языках нет возможности описать объект некорректно, компилятор не позволяет. Даже в динамических, навроде JS или Питона, в них "объект" будет иметь корректный бинарный лейаут, "ошибки структуры объекта" будут сугубо семантические пользовательского уровня.


V>>Объекты в Лиспе не являются первоклассными сущностями языка, вот в чём косяк.

_>Являются.

В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.


V>>Первоклассной сущностью там являются числа/символы, функции и пара CONS(car, cdr), где каждый элемент пары может быть числом, функцией или опять парой (ссылочная семантика для всего перечисленного).

_>И много что ещё. Перечитай определение, что такое first-class citizen.

Сам почитай. ))

В C и C++ нельзя создавать функции во время выполнения программы, поэтому функции не являются объектами первого класса в этих языках. В то же время указатели на функцию можно передавать в качестве аргумента и возвращать из другой функции, поэтому иногда функции в C++ называют объектами второго класса (англ. second-class object). Тем не менее, в C++ есть понятие функционального объекта (англ. function object), который является объектом первого класса и реализует эквивалентную функциям семантику.

Русским по белому, что функциональный объект в С++ — это объект, эмулирующий семантику ф-ий.
Структура функторов в С++ известна, с т.з. компилятора — это пользовательский тип неизвестной семантики.
Т.е. компилятор умывает руки, просто делает то, что описано программистом.


V>>Форт навязывает лишь способ мышления, не навязывая остального.

V>>И при этом чудовищно прост в реализации.
V>>И при этом чудовищно эффективен, в сравнении с Лиспом.
V>>Порой и в сравнении с нейтивом из-за особенностей манипулирования стеком — в Форте обычно меньше паразитных манипуляций данными в стеках/регистрах, меньше ненужных копирований/преобразований данных — состояние стека на выходе одного слова часто является готовым состоянием стека для вызова другого. Писать программы на Форте — это как играть в Тетрис, натурально, все фигуры должны стыковаться, и тогда прога просто летает. ))
_>А уж ассемблер-то как эффективен.

Мимо. Ассемблер низкоуровневый язык, а Форт высокоуровневый, хотя имеет ср-ва низкого уровня.
Это примерно как Си, только потенциально мощнее, т.к. позволяет описывать свой синтаксис (DSL) в программе.

Но это всё не бесплатно, конечно, т.к. простор для ошибок в Форте достаточно широк.
На технике тех лет за эффективность приходилось платить. ))


_>И что из этого вытекает? Зачем все эти Лиспы, Си, Паскали и прочие Форты понапридумывали?


Да тут по классике инженерии — это решения в условиях противоречивых требований, где само решение представляет из себя системы принятых компромиссов.
Развитие ЯВУ диктовалось развитием ресурсов железа, конечно, т.е. сугубо практическими соображениями.
Например, функциональные языки своего слова еще не сказали, т.е. тут как в науке — теория значительно опережает практику. ))

В одном из встреченных экспериментов проводили альфа/бета/эта-преобразования программы (эти преобразования выполняются итеративно, пока есть возможность таких преобразований) на ФП-языке "до самого дна" среднего размера программы.

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

Компиляция на современной технике заняла несколько дней.
Вот тебе ответ, зачем другие языки. ))
Отредактировано 27.03.2023 11:34 vdimas . Предыдущая версия . Еще …
Отредактировано 27.03.2023 10:49 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:47 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:44 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:43 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:40 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:50 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:47 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:44 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:44 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:43 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:42 vdimas . Предыдущая версия .
Re[13]: понимание ООП Алана Кея
От: korvin_  
Дата: 27.03.23 10:08
Оценка: 2 (1)
Здравствуйте, vdimas, Вы писали:

V>Такой дизайн. ))


Какой такой дизайн?

V>Заметь, я не говорил, что в Лиспе нельзя производить трансформацию AST явным образом, если ты напишешь свой некий такой код.


Тогда зачем ты «противопоставил» их Форту?

V>Я напоминаю, как устроены стандарные максросы Лиспа.


Тогда что значит «подстановочные»?

V>Разве?

V>

V>(defmacro foo2 (x)
V> (list 'exp x))


Разве. x — любое S-выражение, либо дерево, либо атом. Обрабатывай как хочешь.

V>В момент компиляции во внутренее представления встречающихся foo2 с аргументом x, де-факто будет происходить подстановка (list 'exp x)) с указанным x.


Потому что ты его так определил.

V>Это обычный подстановочный макрос, который может быть реализован как текстуальный/шаблонный.


Но работает он не с текстом, а с lisp-объектами.

V>На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.


Не вроде.

V>Никакого явного оперирования AST тут нет, как нет оперирования AST в шаблонах С++.


Так ты никак не оперируешь, потому что такой макрос написал. Макрос loop по-твоему тоже никак не оперирует?

(defun bindingp (expr)
  (and (= (length expr) 3)
       (destructuring-bind (var eq-sym val-expr) expr
         (eq eq-sym '=))))

(defmacro my-let (&rest body)
  (loop :while (bindingp (first body))
        :collect (first (first body)) :into variables
        :collect (third (first body)) :into expressions
        :do (setf body (rest body))
        :finally (return `((lambda ,variables ,@body) ,@expressions))))

(defun main ()
  (my-let (x = 1) (y = 2)
    (print (+ x y))))


Или что ты подразумеваешь под «оперированием AST»?

V>Эта функциональность идёт в ввиде отдельной библиотеки-интерпретатора.

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

V>Соответственно, у подключаемого движка-интерпретатора нет никакой связи с твоей программой.

V>В Схеме eval имеет сигнатуру (eval expression environment) где environment необходимо набить ручками перед вызовом eval.
V>Сравнить с Лиспом, в котом eval имеет сигнатуру (eval expression), а environment используется неявным образом текущий.

И что, невозможно выполнить в рантайме, в пользовательском eval Схемы define-syntax?

V>Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.

V>Там очевидно.

Тебе сложно перечислить парочку?

V>>>Но это по-прежнему подстановочные/позиционные макросы, без явного манипулирования AST.

_>>Что мешает работать с syntax object в Схеме через syntax-case, например?

V>Это что-то вроде частичного определения шаблонов в С++ — описание неких частных случаев.

V>Механика та же, что и для общего случая.

ничего общего с шаблонами C++. И это не некие частные случаи, а возможность обрабатывать syntax-object явным образом с помощью обычных функций. Это, как раз общий случай, а syntax-rules — частный, упрощённый способ.

V>Да пофик, динамичность никуда не девается.

V>Всё-равно под капотом работает Лисп-машинка.

Как и Схем-машинка в Схеме.

_>>А eval из Схемы никуда не делся.


V>Он прямо по стандарту делся в отдельную библиотеку, которая подключается при надобности.


По которому стандарту и в какую библиотеку?

Вот смотрю, например, R5RS и не вижу упоминания библиотек.

Вот R7RS, eval наместе. Про библиотеку не сказано.

V>Идея Схемы была дать возможность исполнять конечный код без Лисп-машинки, без ссылочной семантики в >90% случаев, без динамического построения текущего контекста и поиска в нём символов (даже в компиллируемом Лиспе символы ищутся в текущем контексте динамически в процессе работы программы) и т.д.


Откуда ты взял эту идею? У cons-ячеек в Схеме ссылочная семантика и это используется в SICP.


V>>>Для целей эффективного конечного кода пришлось внести в Лисп несколько ограничений, наверно поэтому назвали другим языком и стандарты Схемы с тех пор развиваются независимо от Лиспа.

_>>Схема не так и не для этого разрабатывалась.

V>Для этого. ))

V>

V>Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных


Это не говорит о цели разработки. И к компиляции/интерпретации отношения не имеет.

V>Что исключало необходимость в оперировании динамическим контекстом, позволило производить оптимизацию бинарного представления как в "обычных языках", например, оптимизацию хвостовой рекурсии. Одним словом, Схема дала ср-ва для стирания символов в конечном образе, хотя эти символы живут на этапе компиляции, скажем, в макросах.


SBCL умеет в оптимизацию хвостовой рекурсии. А в Racket есть динамический контекст в виде parameters.

V>Но при надобности не стирать символы — вот вам рядышком прежний динамический eval.



V>>>Вернее, впервые стандарты на семейство Лисп появились в Схеме — именно из-за вносимых ограничений.

_>>С чего ты взял, что из-за них?

V>Для обеспечения указанных св-в программы.

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

Зачем мне ставить себя на их место, какое это имеет отношение к стандратизации?

V>Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризацию по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.


Но при использовании CLOS этого не нужно описывать явно. Всё описано в библиотеке. И какая разница, находится это в библиотеке или зашито в рантайм языка?

V>При этом теряются гарантии целостности, в сравнении с С++, т.е. в эмуляции есть возможность описать объект, имеющий некорректный "бинарный лейаут", на котором эмуляция споткнётся в рантайм (в зависимости от реализации такой эмуляции может быть проход по памяти, либо же динамически выкинута ошибка/сигнал). В ООП-языках нет возможность описать объект некорректно, компилятор не позволяет. Даже в динамических, навроде JS или Питона, в них "объект" будет иметь корректный бинарный лейаут, "ошибки структуры объекта" будут сугубо семантические пользовательского уровня.


Какие гарантии целостности теряются в CLOS?

V>В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.


И что с того? Следует ли тогда стремиться вообще всё известное впихнуть в рантайм языка?

V>>>Первоклассной сущностью там являются числа/символы, функции и пара CONS(car, cdr), где каждый элемент пары может быть числом, функцией или опять парой (ссылочная семантика для всего перечисленного).

_>>И много что ещё. Перечитай определение, что такое first-class citizen.

V>Сам почитай. ))

V>

V>В C и C++ нельзя создавать функции во время выполнения программы, поэтому функции не являются объектами первого класса в этих языках. В то же время указатели на функцию можно передавать в качестве аргумента и возвращать из другой функции, поэтому иногда функции в C++ называют объектами второго класса (англ. second-class object). Тем не менее, в C++ есть понятие функционального объекта (англ. function object), который является объектом первого класса и реализует эквивалентную функциям семантику.

V>Русским по белому, что функциональный объект в С++ — это объект, эмулирующий семантику ф-ий.
V>Структура функторов в С++ известна, с т.з. компилятора — это пользовательский тип неизвестной семантики.
V>Т.е. компилятор умывает руки, просто делает то, что описано программистом.

При чём тут C и C++, когда речь шла о Лиспе? Кроме того непонятно, что автор имел в виду под «созданием функций во время выполнения программы». Что мешает подключить интерпретатор С в программу?
Re[4]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 27.03.23 17:27
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>То же, что и в ожидании возврата из функции Add(2, 5).


В случае с функцией ожидания никакого нет, в том-то и дело. А с сообщением как?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[5]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 01:46
Оценка: +1
Здравствуйте, T4r4sB, Вы писали:
TB>В случае с функцией ожидания никакого нет, в том-то и дело. А с сообщением как?
Точно так же. Сообщение — это метафора, а не физическая реализация.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 01:49
Оценка:
Здравствуйте, vdimas, Вы писали:
V>Если тебе интересно закрыть эту тему раз и навсегда — распишу весь код целиком.

Да, было бы интересно. Брать прямо словарь не обязательно — там много нюансов. Можно использовать какую-то более простую структуру.
Но, сдаётся мне, что иммутабельность будет там обеспечена дизайном типа, а не ключевым словом const.
То есть в лучшем случае вы получите решение, изоморфное System.Collections.Immutable.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 28.03.23 06:53
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Точно так же. Сообщение — это метафора, а не физическая реализация.


И что делать потоку во время этой ожидания этой метафоры? Засыпать?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[7]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 07:18
Оценка:
Здравствуйте, T4r4sB, Вы писали:
TB>И что делать потоку во время этой ожидания этой метафоры? Засыпать?
Повторю свой ответ: то же самое, что делает поток в ожидании возврата из процедуры. Ведь вызов процедуры — это тоже метафора, на самом деле никаких процедур нету, есть только стек и регистры.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 13:01
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Если тебе интересно закрыть эту тему раз и навсегда — распишу весь код целиком.

S>Да, было бы интересно. Брать прямо словарь не обязательно — там много нюансов. Можно использовать какую-то более простую структуру.
S>Но, сдаётся мне, что иммутабельность будет там обеспечена дизайном типа, а не ключевым словом const.

Ключевое слово const даст гарантии сильнее "вербальных соглашений", которые в реальной жизни не обеспечиваются ничем, кроме добросовестности программиста. ))
Даже правильно спроектированный под конкретную задачу тип затем может быть поломан из-за невнимательности, при добавлении мутабельных методов.

Общая идея при конструировании иммутабельных типов от неиммутабельных с использованием паттерна Decorator — это сочетания конструктора перемещения с константным хранением перемещённых данных.
template<typename T>
class ImmutableValue {
    const T value_;

public:
    ImmutableValue(T && value) : value_(value) {}


Добавим делегирующие операторы сравнения для ключей словарей на основе дерева:
    bool operator==(const ImmutableValue<T> & other) const {
        return value_ == other.value_;
    }

    bool operator!=(const ImmutableValue<T> & other) const {
        return value_ != other.value_;
    }
    

    bool operator<(const ImmutableValue<T> & other) const {
        return value_ < other.value_;
    }


Добавим делегирущее вычисление хеша для ключей хеш-таблиц:
    operator const T &() const { return value_; }
};

template<typename T>
struct std::hash<ImmutableValue<T>> {
    size_t operator()(const ImmutableValue<T> & ikey) const {
        return hash<T>()(ikey);
    }    
};


Проверяем:
template<typename T>
size_t hash_value(const T & value) {
    return std::hash<T>()(value);
}

int main() {
    ImmutableKey<int> i = 42;
    std::cout << hash_value(i) << endl;

    return 0;
}


Для оберток типов-отображений (назовём ImmutableMap) общая структура типа будет такая же.
По твоей просьбе опускаю часть контракта (typedef-ы), вот целевой делегирующий метод:

const TValue & operator[](const TKey & key) const { return map_[key]; }


Далее в своём коде используешь шаблонный ImmutableMap.


S>То есть в лучшем случае вы получите решение, изоморфное System.Collections.Immutable.


В С++ ключевое слово const применимо так же к полям структур/классов.
Такие поля могут быть инициализированы только в конструкторе.
Это аналог readonly в C#.

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

Для сравнения, в С++ можно расписать аналогичную функциональность с "железобетонными" гарантиями иммутабельности.

Основные пляски вокруг иммутабельности в ФП, насколько я на них натыкался, они происходят не из-за агоритмов навроде Collections.Immutable (это лишь узкий частный случай), а из-за более широкой задачи — из-за статической и динамической альфа-бета-эта-редукции в процессе компиляции и исполнения программ.

Смотрим, что происходит при частичном связывании аргументов для некоей ф-ии/лямбд:
Func<double, double, double> fn1 = (x, y) => sin(x) + sin(y);
Func<double, double> fn2 = (x) => fn1(x, Math.Pi);

В C# после связывания, тело fn2 будет честно вызывать тело fn1, где снова и снова будет вызываться sin(Math.Pi).

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

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

Для данного выражения имеем sin(Math.Pi)==0, тогда fn2=(x)=>sin(x), тогда через эта-преобразование вызовы fn2(x) заменяются на вызовы просто sin(x).
В С++ подобное редуцирование выполняется лишь для констант времени компиляции и только на некую ограниченную грубину распространения констант.

Вдогонку, возвращаясь к иммутабельным графам — распространение констант (связывание термов) возможно не только в тривиальных случаях, но и по всем данным.
Вплоть до подмены и стирания типов (это про альфа-преобразование — идентично описанные с точностью до "раскрытия скобок" термы-типы можно считать одинаковыми "унутре").
Вплоть до того, что целые куски таких иммутабельных графов, по которым бегает некий вычислитель, могут подменяться просто на значение. ))
Отредактировано 28.03.2023 13:17 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 13:13 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:12 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:08 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:07 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:06 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:04 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:02 vdimas . Предыдущая версия .
Re[12]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 15:05
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>То есть в лучшем случае вы получите решение, изоморфное System.Collections.Immutable.


Вдогонку, у дотнета уже были интерфейсы, описывающие АПИ иммутабельных коллекций, навроде IReadOnlySet и т.д.
Выбранное название неймспейса сбивает с толку, бо там речь не об иммутабельности как таковой, а об эффективных способах порожения read-only коллекций. ))

Например, неэффективной будет реализация на основе некоего FrozenSet, т.к. потребует копирования коллекций целиком при порождении новых коллекций, т.е. "изменения" их в ФП-стиле.

Наверно, стоило этот неймспейс так и назвать FpStyle. ))
Re[13]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 16:04
Оценка:
Здравствуйте, vdimas, Вы писали:
[сcode]
template<typename T>
class ImmutableValue {
const T value_;

public:
ImmutableValue(T && value) : value_(value) {}
[/cсode]
Хм. А там точно будет ссылка на оригинал, а не дубликат? Похоже, вы свой код не проверяли.
А если "исправить" ваш код так:
template<typename T>
class ImmutableValue {
    const T& value_;

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

V>Далее в своём коде используешь шаблонный ImmutableMap.

Вот прямо так сразу использовать шаблонный ImmutableMap не получится. Вы, как водится, привели тот самый фрагмент кода, который был очевиден и без обсуждения.
Как вы реализуете метод add(const TKey & key, const TValue & value)?
Внезапно окажется, что нельзя просто взять произвольный mutable тип, завернуть его в ImmutableValue и наслаждацца. Даже если вы сможете реализовать своё обещание с "zero-overhead" конструктором перемещения (в чём я почему-то сомневаюсь), реализация изменяющих методов "в лоб" потребует от вас реализовать copy-on-write, что убъёт производительность.
Чтобы ваш "мутабельно-иммутабельный" словарь можно было применять в реальной жизни, придётся особенным образом проектировать его мутабельную версию. Посмотрите для примера в код классов ImmutableXXX.Builder всё в том же неймспейсе.


V>В С++ ключевое слово const применимо так же к полям структур/классов.

V>Такие поля могут быть инициализированы только в конструкторе.
V>Это аналог readonly в C#.
Это всё понятно. Да, слово const есть, можно применять его к мемберам. Счастье-то в чём?

V>Это не столько про саму иммутабельность, сколько про специальные алгоритмы на иммутабельных графах и списках.

Чегось? Какие ещё алгоритмы? Какие графы? Там ровно то, что написано в названии неймспейса — реализация иммутабельного списка, словаря, множества, массива, стека, очереди, и ещё пары классов.

V>Соответственно, ценностью этого раздела библиотеки является уже готовая функциональность, а не какие-то там гарантии.

V>(никаких гарантий та система типов не даёт)
Конечно даёт.
Вот мой метод:
public void RegisterState(ImmutableDictionary<string, int> state)
{
  _visited[state] = true;
}

Попробуйте "сломать" его, передав в него мутабельный state.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 28.03.23 16:59
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Повторю свой ответ: то же самое, что делает поток в ожидании возврата из процедуры.


Поток в это время выполняет эту самую процедуру.
А объект и сообщение — это получается по факту просто вызов метода?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[14]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 17:01
Оценка:
Здравствуйте, korvin_, Вы писали:

V>>Такой дизайн. ))

_>Какой такой дизайн?

Примитивный.
Что было ОК для годов разработки Лиспа. ))


V>>Заметь, я не говорил, что в Лиспе нельзя производить трансформацию AST явным образом, если ты напишешь свой некий такой код.

_>Тогда зачем ты «противопоставил» их Форту?

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

Для сравнения, в Форте можно курочить код-как-данные на любой стадии.
Т.е. ТВОЙ высокоуровневый код в телах макросов может работать прямо на стадии компиляции во внутренее представление.
У Лиспа никакого немедленного вызова пользовательских ф-ий в макросах нет и быть не может. ))


V>>Я напоминаю, как устроены стандарные максросы Лиспа.

_>Тогда что значит «подстановочные»?

Это когда некий шаблонный движок по неким правилам производит замену подстрок.
Я же говорил — технически макросы Лиспа могут быть реализованы сугубо текстовой заменой подстрок, а после замены получившаяся новая строка скармливается компилятору.
Примерно так работали первые реализации макросов в Си/С++.
Сейчас там макродвижок работает как в Лиспе — по предкомпилённому представлению.
Но! Это лишь подробности реализации.
Текстуально-шаблонная природа таких макросов целиком определяет их св-ва.


V>>Разве?

V>>

V>>(defmacro foo2 (x)
V>> (list 'exp x))

_>Разве. x — любое S-выражение, либо дерево, либо атом. Обрабатывай как хочешь.

x никак не обрабатывается в момент раскрытия макроса компилятором.
Он может быть обработан уже в рантайме кодом, в который раскрылся макрос.

Можно привести в пример не Форт, а, допустим, Nemerle.
В этом языке тоже есть возможность "вмешиваться в работу компилятора", то бишь в момент раскрытия макроса могут вызываться высокоуровневые примитивы.
Вот там макрос вполне может работать над AST и работает.
В Лиспе — нет, никогда и низачто! ))


V>>В момент компиляции во внутренее представления встречающихся foo2 с аргументом x, де-факто будет происходить подстановка (list 'exp x)) с указанным x.

_>Потому что ты его так определил.

Потому что это семантика сугубо текcтовой шаблонизации.


V>>Это обычный подстановочный макрос, который может быть реализован как текстуальный/шаблонный.

_>Но работает он не с текстом, а с lisp-объектами.

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


V>>На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.

_>Не вроде.

Вроде, вроде.
Абсолютно в точности.


V>>Никакого явного оперирования AST тут нет, как нет оперирования AST в шаблонах С++.

_>Так ты никак не оперируешь, потому что такой макрос написал. Макрос loop по-твоему тоже никак не оперирует?

loop — это конструкт, т.е. встроенная в компилятор хрень.
Или макрос над таким конструктом.

Но может быть реализован в виде обычной ф-ии.
В виде конструкта он сугубо по соображениям эффективности, скорее всего, иначе бы все эти служебные for, to, return должны были бы быть атомами и тело loop должно было бы производить сравнение с ними каждого элемента списка-тела.

Макросы могут содержать другие макросы, раскрываются рекурсивно.
И ты уверен, что loop — это макрос, а не встроенный конструкт?
(в крайней случае макрос над конструктом?)


_>Или что ты подразумеваешь под «оперированием AST»?


Дело не в оперировании AST, а на какой стадии это происходит.
Если в рантайме, то исходная AST зачастую является информационным мусором, лишними данными, занимающими память, т.к. интересует лишь конечное представление.
Например, тело loop со всякими for, to, return — это синтаксис, его не надо хранить явным образом для исполнения.


V>>Соответственно, у подключаемого движка-интерпретатора нет никакой связи с твоей программой.

V>>В Схеме eval имеет сигнатуру (eval expression environment) где environment необходимо набить ручками перед вызовом eval.
V>>Сравнить с Лиспом, в котом eval имеет сигнатуру (eval expression), а environment используется неявным образом текущий.
_>И что

То, что в этом месте вопросы про eval Схемы должны были закончится.


V>>Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.

V>>Там очевидно.
_>Тебе сложно перечислить парочку?

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


_> ничего общего с шаблонами C++. И это не некие частные случаи, а возможность обрабатывать syntax-object явным образом с помощью обычных функций.


Да каких обычных ф-ий? ))
syntax-case выбирает вариант применения шаблона.
То бишь, в одном шаблоне несколько их, syntax-case матчит шаблон.

Просто схема пошла дальше Лиспа и оперирует уже не исходным текстом, а цепочками токенов.
Но какая фиг разница, подстановка чего выполняется — терминалов или нетерминалов?
Т.е. последовательность ASII-символов или токенов из них?
Это все-равно достаточно тупая подстановка.


V>>Да пофик, динамичность никуда не девается.

V>>Всё-равно под капотом работает Лисп-машинка.
_>Как и Схем-машинка в Схеме.

Нифига Схеме "машинка"?
Машинка нужна Лиспу для следующего:
— динамического поиска символов в текущем контексте (угу, даже если это Лисп-компилятор);
— манипулирования этим контектом для каждой ф-ии, чтобы сохранялась семантика, идентичная семантике интерпретатора.
— все данные строго ссылочные, исходные лисповые тела ф-ий хранятся в явном виде имогут быть исполнены через eval в текущем контексте.

И напротив, в Схеме нет оперирования текущим контекстом, нет интерпретатора тел функций, т.е. нет и Схем-машинки (кроме сбоку-припёку, которая опциональна для нужд eval БЕЗ связи с твоей программой, т.е. не нужна в общем случае).

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

Становится не обязательным в большиснстве случаев представлять данные как связанные cons — можно проводить escape-анализ и удалять ссылочную семантику (для банальных чисел) и т.д. до бесконечности.


_>Вот смотрю, например, R5RS и не вижу упоминания библиотек.

_>Вот R7RS, eval наместе. Про библиотеку не сказано.

Возможно. Не принципиально.
Я смотрел несколько реализаций Схем, когда писал свою реализацию для дотнета, видел что eval был оформлен в отдельную либу.


V>>Идея Схемы была дать возможность исполнять конечный код без Лисп-машинки, без ссылочной семантики в >90% случаев, без динамического построения текущего контекста и поиска в нём символов (даже в компиллируемом Лиспе символы ищутся в текущем контексте динамически в процессе работы программы) и т.д.

_>Откуда ты взял эту идею? У cons-ячеек в Схеме ссылочная семантика и это используется в SICP.

Это исходная семантика ссылочная, а компилятор вправе делать преобразования над данными.
Например, зачем тут ссылочная семантика (sin (+ x 5)), если компилятор видит область действия x целиком, т.е. знает, что оно не убегает.


V>>Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных

_>Это не говорит о цели разработки. И к компиляции/интерпретации отношения не имеет.


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


V>>Что исключало необходимость в оперировании динамическим контекстом, позволило производить оптимизацию бинарного представления как в "обычных языках", например, оптимизацию хвостовой рекурсии. Одним словом, Схема дала ср-ва для стирания символов в конечном образе, хотя эти символы живут на этапе компиляции, скажем, в макросах.

_>SBCL умеет в оптимизацию хвостовой рекурсии.

В условиях динамического контекста Лиспа я бы не говорил "гоп" без тщательных экспериментов — какие именно случаи поддаются раскрутке.


V>>Для обеспечения указанных св-в программы.

V>>Там и выхода-то другого не было.
V>>Поставь себя на место разработчиков такой системы, и вопросы отпадут сами собой.
_>Зачем мне ставить себя на их место, какое это имеет отношение к стандратизации?

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


V>>Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризацию по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.

_>Но при использовании CLOS этого не нужно описывать явно. Всё описано в библиотеке.

Это и есть явно, т.е. на библиотечном уровне, а не встроенными ср-вами языка.
В С++ устройство объекта неизвестно, механика диспетчеризации виртуальных ф-ий неизвестна, механика множественного и виртуального множественного наследования неизвестна.
Мы можем знать только реверс-инжинирив конкретные реализации (ну и обладая эрудицией о том, как это вобще реализуется), но с т.з. языка устройство объекта неизвестно.


_>И какая разница, находится это в библиотеке или зашито в рантайм языка?


Говорил уже — компилятор не может породить невалидный лейаут объекта в памяти.
А к тебе в библиотеку запросто можно подать невалидный лейаут объекта.


_>Какие гарантии целостности теряются в CLOS?


Их там просто нет.
Ты можешь случайным образом подать методу одного объекта инстанс другого.


V>>В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.

_>И что с того? Следует ли тогда стремиться вообще всё известное впихнуть в рантайм языка?

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


_>При чём тут C и C++, когда речь шла о Лиспе?


В том абзаце ты возражал против моего понимания первоклассной сущности.
ООП-объекты в Лиспе не являются первоклассными.


_>Кроме того непонятно, что автор имел в виду под «созданием функций во время выполнения программы».


Лямбды, карринг.


_>Что мешает подключить интерпретатор С в программу?


Зачем интерпретатор? ))

В С++ можно описывать функциональные объекты — функторы.
Но они не являются первоклассными сущностями-функциями, они являются первоклассными сущностями-объектами, у которых определён метод 'operator()'.
Отредактировано 28.03.2023 17:59 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 17:21 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 17:03 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 17:02 vdimas . Предыдущая версия .
Re[15]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 18:11
Оценка: +1
Здравствуйте, vdimas, Вы писали:

V>Макросы Лиспа работают на стадии компиллирования текста


Нет. Макросы Лиспа работают после read, который преобразует текст в S-выражение.

V> т.е. макросы обслуживаются низкоуровневым (типа ассемблерным или сишным) кодом, смотря на чём реализация Лиспа была написана.


Макросы Лиспа — это обычные функции, которые выполняются во время компиляции, получают на вход невычесленное S-выражение и преобразуют/обрабатывают его ровно так, как это описано в теле макроса.

V> Твои высокоуровневые примитивы не вызываются.


Какие мои высокоуровневые примитивы не вызываются? В теле макроса вызывается любой Лисп-код, который я напишу.

V> Трансформацию AST можно выполнять лишь уже в процессе работы Лисп-машинки твоим высокоуровневым лисповым кодом, к макросам это никаким боком.


Эм нет. В процессе работы Лисп-машины никакого AST уже может не быть, а быть только байткод или машкод.

V>Для сравнения, в Форте можно курочить код-как-данные на любой стадии.


И?

V>Т.е. ТВОЙ высокоуровневый код в телах макросов может работать прямо на стадии компиляции во внутренее представление.


И? А если я вызываю compile в рантайме — это стадия компиляции или стадия выполнения?

V>У Лиспа никакого немедленного вызова пользовательских ф-ий в макросах нет и быть не может. ))


Т.е. ты совершенно не понимаешь, как работают макросы в Лиспе. Именно вызов функции там и происходит.

V>>>Я напоминаю, как устроены стандарные максросы Лиспа.

_>>Тогда что значит «подстановочные»?

V>Это когда некий шаблонный движок по неким правилам производит замену подстрок.


Значит, не понимаешь. В CL нет никакого шаблонного движка.

V>Я же говорил — технически макросы Лиспа могут быть реализованы сугубо текстовой заменой подстрок, а после замены получившаяся новая строка скармливается компилятору.


Не могут.

V>Примерно так работали первые реализации макросов в Си/С++.


Но к Лиспу они отношения не имеют.

V>Сейчас там макродвижок работает как в Лиспе — по предкомпилённому представлению.

V>Но! Это лишь подробности реализации.
V>Текстуально-шаблонная природа таких макросов целиком определяет их св-ва.

Ты что-то перепутал.

V>>>Разве?

V>>>

V>>>(defmacro foo2 (x)
V>>> (list 'exp x))

_>>Разве. x — любое S-выражение, либо дерево, либо атом. Обрабатывай как хочешь.

V>x никак не обрабатывается в момент раскрытия макроса компилятором.

V>Он может быть обработан уже в рантайме кодом, в который раскрылся макрос.

Вообще-то ты вызываешь здесь функцию list в compile-time и никакого вызова list в рантайме (в коде, в который раскрылся макрос) уже не будет. Возьми macroexpand, да посмотри.

V>Можно привести в пример не Форт, а, допустим, Nemerle.

V>В этом языке тоже есть возможность "вмешиваться в работу компилятора", то бишь в момент раскрытия макроса могут вызываться высокоуровневые примитивы.

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

V>Вот там макрос вполне может работать над AST и работает.

V>В Лиспе — нет, никогда и низачто! ))

Ты бредишь.

V>Потому что это семантика сугубо текcтовой шаблонизации.




V>Макрос не работает с лисп-объектами, он работает с текстовым представлением исходника.


Нет. Возьми любую книгу по CL да почитай. Или сам macroexpand'ом воспользуйся, да проверь, что попадёт в результат, а что — нет.

V>В этом месте я начинаю подозревать, что ты путаешь работу макроса и работу кода, порождённого макросом. ))


Нет, просто ты совершенно не понимаешь, как работают макросы в Лиспе.

V>>>На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.

_>>Не вроде.

V>Вроде, вроде.

V>Абсолютно в точности.

Совершенно нет.

V> loop — это конструкт, т.е. встроенная в компилятор хрень.

V> Или макрос над таким конструктом.

Выполни macroexpand да посмотри. А заодно можешь посмотреть реализацию.

V>Но может быть реализован в виде обычной ф-ии.


Может, но придётся обмазываться лямбдами, чтобы отложить вычисления выражений.

V>В виде конструкта он сугубо по соображениям эффективности, скорее всего, иначе бы все эти служебные for, to, return должны были бы быть атомами и тело loop должно было бы производить сравнение с ними каждого элемента списка-тела.


Посмотри реализацию и macroexpand.

V>Макросы могут содержать другие макросы, раскрываются рекурсивно.

V>И ты уверен, что loop — это макрос, а не встроенный конструкт?

Да.

V>(в крайней случае макрос над конструктом?)


Над каким конструктом?

_>>Или что ты подразумеваешь под «оперированием AST»?


V>Дело не в оперировании AST, а на какой стадии это происходит.

V>Если в рантайме, то исходная AST зачастую является информационным мусором, лишними данными, занимающими память, т.к. интересует лишь конечное представление.
V>Например, тело loop со всякими for, to, return — это синтаксис, его не надо хранить явным образом для исполнения.

А если в compile-time?

V>То, что в этом месте вопросы про eval Схемы должны были закончится.


Ты на них не ответил.

V>>>Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.

V>>>Там очевидно.
_>>Тебе сложно перечислить парочку?

V>Мне сложно отвечать на слабосформулированные вопросы, бо обсуждение теряет темп и смысл.

V>Нормальное обсуждение состоит из тезисов и антитезисов.
V>Есть что сказать — говори.

Пока что я могу сказать, что ты не понимаешь предмета разговора.

_>> ничего общего с шаблонами C++. И это не некие частные случаи, а возможность обрабатывать syntax-object явным образом с помощью обычных функций.


V>Да каких обычных ф-ий? ))


Например syntax-car, syntax-cdr и так далее.

V>syntax-case выбирает вариант применения шаблона.


Лишь для удобства, а можно просто матчить с _ и обрабатывать функциями. Любыми доступными и/или опредедёнными.

(define-syntax let
  (lambda (x)
    (define ids?
      (lambda (ls)
        (or (null? ls)
            (and (identifier? (car ls)) (ids? (cdr ls))))))
    (define unique-ids?
      (lambda (ls)
        (or (null? ls)
            (and (not (memp
                        (lambda (x) (bound-identifier=? x (car ls)))
                        (cdr ls)))
                 (unique-ids? (cdr ls))))))
    (syntax-case x ()
      [(_ ((i e) ...) b1 b2 ...)
       (and (ids? #'(i ...)) (unique-ids? #'(i ...)))
       #'((lambda (i ...) b1 b2 ...) e ...)])))


With this mechanism, transformers are procedures of one argument. The argument is a syntax object representing the form to be processed. The return value is a syntax object representing the output form. A syntax object may be any of the following.

a nonpair, nonvector, nonsymbol value,
a pair of syntax objects,
a vector of syntax objects, or
a wrapped object.



V>Просто схема пошла дальше Лиспа и оперирует уже не исходным текстом, а цепочками токенов.


Лисп не оперирует исходным текстом.

V>Но какая фиг разница, подстановка чего выполняется — терминалов или нетерминалов?

V>Т.е. последовательность ASII-символов или токенов из них?
V>Это все-равно достаточно тупая подстановка.

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

V>>>Да пофик, динамичность никуда не девается.

V>>>Всё-равно под капотом работает Лисп-машинка.
_>>Как и Схем-машинка в Схеме.

V>Нифига Схеме "машинка"?


За тем же, зачем и любому другому языка.

V>Машинка нужна Лиспу для следующего:

V>- динамического поиска символов в текущем контексте (угу, даже если это Лисп-компилятор);
V>- манипулирования этим контектом для каждой ф-ии, чтобы сохранялась семантика, идентичная семантике интерпретатора.
V>- все данные строго ссылочные, исходные лисповые тела ф-ий хранятся в явном виде имогут быть исполнены через eval в текущем контексте.

Не хранятся. Пока жи ка мне как мне получить тело функции в CL.

V>И напротив, в Схеме нет оперирования текущим контекстом, нет интерпретатора тел функций, т.е. нет и Схем-машинки (кроме сбоку-припёку, которая опциональна для нужд eval БЕЗ связи с твоей программой, т.е. не нужна в общем случае).


Это зависит сугубо от реализации.

V>В общем, бета-эта-редукции в Схеме выполняются аж бегом компилятором.


Или не выполняются, если компилятора нет.

V>Когда не требуется явно оперировать контекстом исполнения и символы стираются (т.к. область видимости статическая, т.е. символы ресолвятся на этапе компиляции, а не в процессе работы) — у компилятора развязываются руки.


V>Становится не обязательным в большиснстве случаев представлять данные как связанные cons — можно проводить escape-анализ и удалять ссылочную семантику (для банальных чисел) и т.д. до бесконечности.


При чём тут cons-ячейки? И какое отношение это имеет к ссылочной семантике?

_>>Вот смотрю, например, R5RS и не вижу упоминания библиотек.

_>>Вот R7RS, eval наместе. Про библиотеку не сказано.

V>Возможно. Не принципиально.

V>Я смотрел несколько реализаций Схем, когда писал свою реализацию для дотнета, видел что eval был оформлен в отдельную либу.

А всё остальное было в одном жирном файле с кодом?

V>>>Идея Схемы была дать возможность исполнять конечный код без Лисп-машинки, без ссылочной семантики в >90% случаев, без динамического построения текущего контекста и поиска в нём символов (даже в компиллируемом Лиспе символы ищутся в текущем контексте динамически в процессе работы программы) и т.д.

_>>Откуда ты взял эту идею? У cons-ячеек в Схеме ссылочная семантика и это используется в SICP.

V>Это исходная семантика ссылочная, а компилятор вправе делать преобразования над данными.

При чём тут компилятор?

V>Например, зачем тут ссылочная семантика (sin (+ x 5)), если компилятор видит область действия x целиком, т.е. знает, что оно не убегает.

Затем, например, что x может быть как int'ом, а может — bignum и известно это будет только в рантайме. Сколько место на стэке для вызова + выделит компилятор под x?

V>>>Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных

_>>Это не говорит о цели разработки. И к компиляции/интерпретации отношения не имеет.

V>

V>Статическая область видимости символов позволяет отказаться от хранения их имён в скомпиллированном образе, т.к. все символы ресолвятся еще на стадии компиляции.
V>А значит, не нужен пресловутый лисповый контекст, в котором хрен ты так просто вызовешь некую функцию fn, сначала Лисп-машинка найдёт в этом контексте текущее (т.е. ближайшее) определение символа fn, убедится, что это функция, и подаст ей хвост выражения.

Именно так и работает интерпретатор Схемы в SICP.

V>>>Что исключало необходимость в оперировании динамическим контекстом, позволило производить оптимизацию бинарного представления как в "обычных языках", например, оптимизацию хвостовой рекурсии. Одним словом, Схема дала ср-ва для стирания символов в конечном образе, хотя эти символы живут на этапе компиляции, скажем, в макросах.

_>>SBCL умеет в оптимизацию хвостовой рекурсии.

V>В условиях динамического контекста Лиспа я бы не говорил "гоп" без тщательных экспериментов — какие именно случаи поддаются раскрутке.


Ну так поэкспериментируй. Заодно расскажешь, как это Racket работает с динамическим контекстом и оптимизацией хвостовых вызовов.

V>Наверно такое, что интерпретатор Лиспа — это дешевая в разработке хрень (несколько вечеров на коленке достаточно)


Ты когда последний раз стандарт Common Lisp открывал?

V>а компилятор Схемы — дорогая разработка.

V>Соотв., стандартизация нужна, чтобы дорогой труд не пропадал даром.

никакой стандарт не мотивирован таким образом.

V>>>Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризацию по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.

_>>Но при использовании CLOS этого не нужно описывать явно. Всё описано в библиотеке.

V>Это и есть явно, т.е. на библиотечном уровне, а не ср-вами языка.


И что? Чем это мешает?

V>В С++ устройство объекта неизвестно, механика диспетчеризации виртуальных ф-ий неизвестна, механика множественного и виртуального множественного наследования неизвестна.

V>Мы можем знать только реверс-инжинирив конкретные реализации (ну и обладая эрудицией о том, как это вобще реализуется), но с т.з. языка устройство объекта неизвестно.

И что?

_>>И какая разница, находится это в библиотеке или зашито в рантайм языка?


V>Говорил уже — компилятор не может породить невалидный лейаут объекта в памяти.


Вообще может.

V>А к тебе в библиотеку запросто можно подать невалидный лейаут объекта.


Каким образом? Продемонстрируй.

_>>Какие гарантии целостности теряются в CLOS?


V>Их там просто нет.


С чего это нет?

V>Ты можешь случайным образом подать методу одного объекта инстанс другого.


В CLOS методы не принадлежат объектам. Ты и про CLOS, похоже, ничего не знаешь.

И представь себе, во всём ООП так происходит: методу объекта базового класса можно «подать» инстанс любого наследника.

V>>>В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.

_>>И что с того? Следует ли тогда стремиться вообще всё известное впихнуть в рантайм языка?

V>И в компилятор, и в рантайм.


Супер. Запихни youtube в рантайм.

_>>При чём тут C и C++, когда речь шла о Лиспе?


V>В том абзаце ты возражал против моего понимания первоклассной сущности.

V>ООП-объекты в Лиспе не являются первоклассными.

Является. Ты так ничего и не понял.

_>>Кроме того непонятно, что автор имел в виду под «созданием функций во время выполнения программы».


V>Лямбды, карринг.


Это такие же предкомпилированные функциональные объекткы и указатели на функции, как и в C++.

_>>Что мешает подключить интерпретатор С в программу?


V>Зачем интерпретатор? ))


V>В С++ можно описывать функциональные объекты — функторы.

V>Но они не являются первоклассными сущностями-функциями, они являются первоклассными сущностями-объектами, у которых определён метод 'operator()'.

Ты демагогией занимаешься. Функторы в C++ — first-class citizen.
Re[15]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 18:18
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Ты можешь случайным образом подать методу одного объекта инстанс другого.


А в SmallTalk'е ООП первоклассное? Там нельзя вызвать любой произвольный метод у любого объекта? Ошибка компиляции будет?
Re[15]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 18:32
Оценка:
Здравствуйте, vdimas, Вы писали:

_>>Какие гарантии целостности теряются в CLOS?


V>Ты можешь случайным образом подать методу одного объекта инстанс другого.


Это не отвечает на вопрос, какие гарантии целостности в этом случае нарушатся.

Если я в Схеме функции + передам не число, это нарушит гарантии целостности?
Re[14]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 19:19
Оценка:
Здравствуйте, Sinclair, Вы писали:
S> ImmutableValue(T && value) : value_(value) {}
S>Хм. А там точно будет ссылка на оригинал, а не дубликат?

Там будет и не ссылка, и не дубликат.


S>А если "исправить" ваш код так:

S> const T& value_;

Тогда мы храним ссылку, а не значение, т.е. теряем контроль над другими потенциальными ссылками на это значение.
Я ж не просто так храню именно значение. ))

Семантика перемещения активно использовалась в С++ еще до поддержки её в стандарте C++11, это популярный паттерн проектирования для плюсов, особенно в сценарии read-modify-write.
Соответственно, однажды поддержали в синтаксисе языка.

До этого выкручивались через swap, либо расписывали перемещающую семантику ручками.
swap изначально был реализован с перемещающей семантикой, например:
std::vector<int> v1;
std::vector<int> v2;
std::swap(v1, v2);

в процессе обмена содержимым переменных копии данных не создаются, вектора поэлементно не копируются.
Происходит обмен 3-х указателей: begin_, end_, capacity_end_;


S>Покажите мне пример кода, в котором вы изготавливаете из мутабельного объекта иммутабельный, и продемонстрируйте, что вы после этого не можете этот иммутабельный изменить.


template<typename T>
class ImmutableValue {
    const T value_;
public:
    ImmutableValue(T && value) : value_(move(value)) {}
    const T & value() const { return value_; }
};

int main()
{
    using namespace std;
    typedef ImmutableValue<string> istring;

    string s1 = "it is a long enough string";
    istring s2 = move(s1);
    assert(s2.value() == "it is a long enough string");
    assert(s1 == "");

    s1 = "42";
    assert(s2.value() == "it is a long enough string");
    assert(s1 == "42");

    return 0;
}


Здесь "it is a long enough string/0" выделяется в динамической памяти, затем через std::move перемещается в иммутабельную строку s2, при этом строка s1 теряет своё содержимое.
Далее мы изменяем значение строки s1, в то время как зачение s2 не изменяется.
Изменить законными способами значение переменной s2 нельзя;

    istring s3 = s2; // создание копии строки
    assert(s3.value() == "it is a long enough string");
    assert(s2.value().data() != s3.value().data()); // именно копии, по разным адресам

    s2 = s3; // ошибка компиляции: operator=(const ImmutableValue<std::string> &) удалён


Последняя строка тоже важна для понимания — получив однажды ссылку на ImmutableValue<std::string> я могу быть уверен, что переменную не затрут.


V>>Далее в своём коде используешь шаблонный ImmutableMap.

S>Вот прямо так сразу использовать шаблонный ImmutableMap не получится. Вы, как водится, привели тот самый фрагмент кода, который был очевиден и без обсуждения.
S>Как вы реализуете метод add(const TKey & key, const TValue & value)?

Никак.
Для иммутабельных типов это будут внешние ф-ии и операторы, разумеется.
Просто в дотнете сплошные методы. ))


S>Внезапно окажется, что нельзя просто взять произвольный mutable тип, завернуть его в ImmutableValue и наслаждацца.


Можно.


S>Даже если вы сможете реализовать своё обещание с "zero-overhead" конструктором перемещения (в чём я почему-то сомневаюсь)


Зря сомневаешься.


S>реализация изменяющих методов "в лоб" потребует от вас реализовать copy-on-write, что убъёт производительность.


Это смотря, какова природа/структура и объемы данных.
copy-on-write для небольших объемов линейно-расположенных в памяти данных работает лучше иммутабельного графа.
У меня выходило что-то до 2-3-х десятков машинных слов выгодней было создавать линейные копии.

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


S>Чтобы ваш "мутабельно-иммутабельный" словарь можно было применять в реальной жизни, придётся особенным образом проектировать его мутабельную версию.


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

Я имено сегодня прошёлся по нововведениям 8-й версии и малость поржал, бо там описали аккурат предложенный мною сценарий.
http://www.rsdn.org/forum/flame.comp/8494901.1
"У дураков мысли сходятся" (С)


S>Посмотрите для примера в код классов ImmutableXXX.Builder всё в том же неймспейсе.


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

Разумеется, я и сам не раз использовал аналогичные типы данных, например, в утилите описания лексических анализаторов, там узлы графов состояний НКА и ДКА.
Т.е., эта техника была отшлифована в ФП еще черти когда.

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


V>>В С++ ключевое слово const применимо так же к полям структур/классов.

V>>Такие поля могут быть инициализированы только в конструкторе.
V>>Это аналог readonly в C#.
S>Это всё понятно.

Всё это обсуждение подзатянулось из-за того, что это было непонятно тебе.
В C# ключевое слово const у мемберов объявляет аналог статического readonly поля объекта.

В С++ это аналог readonly во всех случаях.
В случае статической инициализации поля, если компилятор видит эту инициализацию, он сразу подставляет видимое значение.


S>Да, слово const есть, можно применять его к мемберам. Счастье-то в чём?


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


V>>Это не столько про саму иммутабельность, сколько про специальные алгоритмы на иммутабельных графах и списках.

S>Чегось? Какие ещё алгоритмы? Какие графы? Там ровно то, что написано в названии неймспейса — реализация иммутабельного списка, словаря, множества, массива, стека, очереди, и ещё пары классов.

Проснись, коллега. ))
Это именно реализация некоторых иммутабельных алгоритмов над некоторыми иммутабельными структурами данных.

Collections.Immutable не приносит в язык иммутабельность как таковую, эта либа лишь реализует некоторые структуры и алгоритмы, доказавшие свою полезность в ФП.


V>>Соответственно, ценностью этого раздела библиотеки является уже готовая функциональность, а не какие-то там гарантии.

V>>(никаких гарантий та система типов не даёт)
S>Конечно даёт.

Не-а.
Я могу нарушать гарантии интерфейсов, например, реализовывать тип как мутабельный и возвращать this из методов.


S>Вот мой метод:

S>
S>public void RegisterState(ImmutableDictionary<string, int> state)
S>{
S>  _visited[state] = true;
S>}
S>

S>Попробуйте "сломать" его, передав в него мутабельный state.

У тебя ошибка, должно быть так:
_visited = _visited.SetItem(state, true);

Возвращается ссылка на другой экземпляр.
(который в общем случае включает узлы исходного дерева)

И это у тебя оно гарантируется из-за использования конкретного типа ImmutableDictionary<,>, а если сделать так:
public void RegisterState<TDict, TKey, TValue>(TDict state)
    where TDict : IImmutableDictionary<TKey, TValue> {...}

то прощай гарантии, можно начинать хулиганить. ))

В плюсах, напротив, можно определить некий концепт ImmutableMap и юзать так:
template<ImmutableMap TDict>
void RegisterState(TDict state) {
   visited_ = merge(visited_, state, true);
}

Где концепт ImmutableMap требует, например, быть инстансом шаблонного типа ImmutableMapWrapper, которому в кач-ве аргумента подали тип, удовлетворяющий публичному интерфейсу std::map.

Что касается ключевой фишки иммутабельных списков/графов, т.е. что касается шаренья узлов между инстансами, этого можно достичь или через счётчики ссылок, или через техники навроде региональной памяти, чтобы не возиться со счётчиками.
Отредактировано 28.03.2023 19:29 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 19:29 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 19:24 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 19:23 vdimas . Предыдущая версия .
Re[16]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 19:53
Оценка:
Здравствуйте, korvin_, Вы писали:

V>>Expression<T> и его наследники не являются first class citizen.

V>>С т.з. языка это "пользовательские типы данных".
V>>В общем, требование first class citizen надуманное.
_>First-class citizen и built-in — ортогональные вещи.
_>First-class citizen — это:
_>

_>Robin Popplestone gave the following definition: All items have certain fundamental rights.
_>All items can be the actual parameters of functions
_>All items can be returned as results of functions
_>All items can be the subject of assignment statements
_>All items can be tested for equality.

_>последний пункт неоднозначный, но «пользовательские/встроенные типы данных» тут совершенно не при чём.

Что показывает, что ретроспектива развития IT, об которую мы тут зацепились, очень даже на пользу. ))

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

Например, в языке Си:
typedef struct {
    int i, j;
} S;

S func1(S arg); // ошибка компиляции


Но можно передать по указателю:
typedef struct {
    int i, j;
} S;

void func1(const S * arg, S * result); // OK

Соответственно, структура S не являлась первоклассной сущностью, а указатель являлся.

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

И да, на твоё замечание я уже отвечал рядом:

V>>Expression<T> и его наследники не являются first class citizen.
НС>В языке C# — являются.

Только в том смысле, что наследуются от Object, который first class citizen.
Так можно про любой GC-тип дотнета сказать. ))

Строго говоря, первоклассной сущностью в C# являются ссылки на объекты, но не сами объекты, внезапно.
Ну и, я считаю объекты Object, Delegate, String, Array тоже первоклассными, помимо примитивных типов.

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

Плюс целый ряд допущений/ограничений был сделан для value-type, чтобы сделать его первоклассной сущностью, а именно: дефолтная инициализация памяти структуры байтами-нулями, дефолтное побитовое копирование содержимого структур, невозможность переопределения перечисленного.

В общем, Expression<T> не являются отдельной первоклассной сущностью AST, по крайней мере не большей, чем некий MyExpression<T>, то бишь никакого уникального признака "first citizen" у них нет. И даже не являются встроенными типами неизвестной структуры. Это просто вынужденный костыль, бо разработка Roslyn была только в проекте.

Для сравнение, результат typeof(T) в дотнете можно с некоторой натяжкой назвать отдельной первоклассной сущностью.
Хотя тип Type и описан как "обычный объект", но это просто обертка (Decorator) к типу неизвестной (непрозрачной) структуры, представляющей из себя бинарные кишки платформы.
Такими непрозрачными типами являются TypeHandle, GCHandle и т.д.

Напротив, иерархия Expression<T> строится на абсолютно прозрачных типах юзверского уровня, и определение этих типов, разумеется, даётся в библиотечном виде.
Отредактировано 28.03.2023 20:08 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 20:05 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 20:02 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 20:00 vdimas . Предыдущая версия .
Re[17]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 20:48
Оценка: +2 :)
Здравствуйте, vdimas, Вы писали:

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


При чём тут мемы?

V>Например, в языке Си:

V>
V>typedef struct {
V>    int i, j;
V>} S;

V>S func1(S arg); // ошибка компиляции
V>


V>Но можно передать по указателю:

V>
V>typedef struct {
V>    int i, j;
V>} S;

V>void func1(const S * arg, S * result); // OK
V>

V>Соответственно, структура S не являлась первоклассной сущностью, а указатель являлся.

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


Какая неизвестная структура у указателя?

V>"Ортогональность" появилась позже, по мере развития ЯВУ, но тогда и определение надо было доработать.


Определение и доработали, я приводил цитату из 4-х пунктов.

V>И да, на твоё замечание я уже отвечал рядом:

V>

V>>>Expression<T> и его наследники не являются first class citizen.
НС>>В языке C# — являются.

V>Только в том смысле, что наследуются от Object, который first class citizen.
V>Так можно про любой GC-тип дотнета сказать. ))

V>Строго говоря, первоклассной сущностью в C# являются ссылки на объекты, но не сами объекты, внезапно.

Строго говоря, это словоблудие. Косвенность сегодня никого особо не волнует.

V>В общем, Expression<T> не являются отдельной первоклассной сущностью AST, по крайней мере не большей, чем некий MyExpression<T>, то бишь никакого уникального признака "first citizen" у них нет. И даже не являются встроенными типами неизвестной структуры. Это просто вынужденный костыль, бо разработка Roslyn была только в проекте.


V>Для сравнение, результат typeof(T) в дотнете можно с некоторой натяжкой назвать отдельной первоклассной сущностью.

V>Хотя тип Type и описан как "обычный объект", но это просто обертка (Decorator) к типу неизвестной (непрозрачной) структуры, представляющей из себя бинарные кишки платформы.
V>Такими непрозрачными типами являются TypeHandle, GCHandle и т.д.



V>Напротив, иерархия Expression<T> строится на абсолютно прозрачных типах юзверского уровня, и определение этих типов, разумеется, даётся в библиотечном виде.


Ещё раз: «прозрачность» не имеет никакого значения для определения, что является first-class citizen, а что — нет.

P. S. Дополнение к примеру syntax-case:

(library (syntax-utils)

  (export syntax-car
          syntax-cdr
          syntax-cadr
          syntax-null?
          ids?
          unique-ids?)
  (import (rnrs))

  (define (syntax-car x)
    (syntax-case x ()
      ((car . cdr) #'car)))

  (define ids?
    (lambda (ls)
      (or (null? ls)
          (and (identifier? (car ls)) (ids? (cdr ls))))))
    
  (define unique-ids?
    (lambda (ls)
      (or (null? ls)
          (and (not (memp
                     (lambda (x) (bound-identifier=? x (car ls)))
                     (cdr ls)))
               (unique-ids? (cdr ls))))))
    
  (define (syntax-cdr x)
    (syntax-case x ()
      ((car . cdr) #'cdr)))

  (define (syntax-cadr x)
    (syntax-car (syntax-cdr x)))
    
  (define (syntax-null? x)
    (syntax-case x ()
      (()          #t)
      ((car . cdr) #f)))

  'end)

...
(import (rnrs)
        (for (r6rs-ext syntax-utils) expand))

(define-syntax let
  (lambda (x)
    (syntax-case x ()
      ((_ . body)
       (if (syntax-null? (syntax-cdr #'body))
           (error 'let "invalid syntax")
           (let loop ((vars    '())
                      (exprs   '())
                      (bindings (syntax-car #'body)))
             (cond ((not (syntax-null? bindings))
                    (let* ((binding (syntax-car bindings))
                           (var  (syntax-car  binding))
                           (expr (syntax-cadr binding)))
                      (loop (cons var   vars)
                            (cons expr  exprs)
                            (syntax-cdr bindings))))
                   ((and (ids? vars) (unique-ids? vars))
                    #`((lambda #,(reverse vars) #,@(syntax-cdr #'body)) #,@(reverse exprs)))
                   (else
                    (error 'let "invalid syntax")))))))))


(let ((x 1)
      (y 2))
  (display (+ x y))
  (newline))

— всё, никаких шаблонов. Да и syntax-case можно убрать вообще, убрав даже (_ . body)
Re[17]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 21:00
Оценка:
Здравствуйте, vdimas, Вы писали:

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

V>"Ортогональность" появилась позже, по мере развития ЯВУ, но тогда и определение надо было доработать.

Какая неизвестная структура у int? А если б была известная, то компилятор не брал бы на себя и программист ручками электроны двигал? Или как ты себе это представляешь?
А сейчас структуры в Си можно передавать по значению? Они перестали быть известными?
Re[13]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 29.03.23 05:11
Оценка: :)
Здравствуйте, vdimas, Вы писали:
V>Вдогонку, у дотнета уже были интерфейсы, описывающие АПИ иммутабельных коллекций, навроде IReadOnlySet и т.д.
Вы по-прежнему не понимаете смысл иммутабельности. IReadOnlySet, IReadOnlyList и прочие никакого отношения к иммутабельности не имеют.
V>Выбранное название неймспейса сбивает с толку, бо там речь не об иммутабельности как таковой, а об эффективных способах порожения read-only коллекций. ))
Нет. Речь там именно об иммутабельности, а не о read-only.

V>Например, неэффективной будет реализация на основе некоего FrozenSet, т.к. потребует копирования коллекций целиком при порождении новых коллекций, т.е. "изменения" их в ФП-стиле.

А то.

V>Наверно, стоило этот неймспейс так и назвать FpStyle. ))

Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 29.03.23 05:12
Оценка:
Здравствуйте, T4r4sB, Вы писали:
TB>А объект и сообщение — это получается по факту просто вызов метода?
Да, это — самая эффективная реализация ожидания обработки сообщения, на которое нам нужен синхронный ответ.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[15]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 29.03.23 05:48
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Здравствуйте, Sinclair, Вы писали:

S>> ImmutableValue(T && value) : value_(value) {}
S>>Хм. А там точно будет ссылка на оригинал, а не дубликат?

V>Там будет и не ссылка, и не дубликат.


V>Тогда мы храним ссылку, а не значение, т.е. теряем контроль над другими потенциальными ссылками на это значение.

V>Я ж не просто так храню именно значение. ))

S>>Покажите мне пример кода, в котором вы изготавливаете из мутабельного объекта иммутабельный, и продемонстрируйте, что вы после этого не можете этот иммутабельный изменить.


V>
V>template<typename T>
V>class ImmutableValue {
V>    const T value_;
V>public:
V>    ImmutableValue(T && value) : value_(move(value)) {}
V>    const T & value() const { return value_; }
V>};

V>int main()
V>{
V>    using namespace std;
V>    typedef ImmutableValue<string> istring;

V>    string s1 = "it is a long enough string";
V>    istring s2 = move(s1);
V>    assert(s2.value() == "it is a long enough string");
V>    assert(s1 == "");

V>    s1 = "42";
V>    assert(s2.value() == "it is a long enough string");
V>    assert(s1 == "42");

V>    return 0;
V>}
V>

Понятно. Вижу, вы исправили ошибку — теперь у вас в коде 2 move. Исходный код, который вы привели, падал на assert(s1 == "").

V>Здесь "it is a long enough string/0" выделяется в динамической памяти, затем через std::move перемещается в иммутабельную строку s2, при этом строка s1 теряет своё содержимое.


V>Далее мы изменяем значение строки s1, в то время как зачение s2 не изменяется.

V>Изменить законными способами значение переменной s2 нельзя;
Осталось понять, что изменится, если мы поубираем ключевые слова const из вашего шаблонного кода. Перестанет ли isting быть иммутабельным?

V>
V>    istring s3 = s2; // создание копии строки
V>    assert(s3.value() == "it is a long enough string");
V>    assert(s2.value().data() != s3.value().data()); // именно копии, по разным адресам

V>    s2 = s3; // ошибка компиляции: operator=(const ImmutableValue<std::string> &) удалён
V>

V>Последняя строка тоже важна для понимания — получив однажды ссылку на ImmutableValue<std::string> я могу быть уверен, что переменную не затрут.
Вот этот фрагмент непонятен. Зачем вы производите копии иммутабельного объекта? Он же гарантированно иммутабельный, это как раз и даёт ссылочную прозрачность.

V>>>Далее в своём коде используешь шаблонный ImmutableMap.

S>>Вот прямо так сразу использовать шаблонный ImmutableMap не получится. Вы, как водится, привели тот самый фрагмент кода, который был очевиден и без обсуждения.
S>>Как вы реализуете метод add(const TKey & key, const TValue & value)?
V>Никак.
V>Для иммутабельных типов это будут внешние ф-ии и операторы, разумеется.
V>Просто в дотнете сплошные методы. ))
Методы гораздо удобнее выстраивать в цепочку. Запись вида list.add(1).add(2).add(42) читать гораздо комфортнее, чем add(add(add(list, 1), 2), 42))).
Но это дело вкуса. Покажите, как вы будете реализовывать внешний метод add.

V>Можно.



V>Зря сомневаешься.

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

V>Это смотря, какова природа/структура и объемы данных.


V>copy-on-write для небольших объемов линейно-расположенных в памяти данных работает лучше иммутабельного графа.
Началось виляние. Узнаю коллегу вдимаса.
V>Но это мы уже ответвились в другую область — в область алгоритмов и структур данных, а они зависят от конкретных задач (или классов задач, бо практически все задачи уже известны, бгг).
Конкретная задача хорошо известна — реализовать иммутабельные структуры так, чтобы они были а) надёжными, б) эффективными.

V>В моём случае не придётся для сценария, скажем, для которого разработали FrozenDictionary в 8-м дотнете.

Да, для этого экзотического сценария можно сделать реализацию эффективнее, чем ImmutableDictionary. Обратите внимание на две вещи:
1. Ваша реализация оказалась уместна только для экзотического частного случая; для более общего частного случая она непригодна.
2. В дотнете как-то обошлись без магии ключевого слова const. Выходит, любые виды неизменности реализуемы и без него. Сюрприз-сюрприз!

V>И странно, что оно не было сделано еще лет 20 назад, когда тема иммутабельных деревьев была модной на слуху.

Сначала удовлетворяются потребности 90% аудитории. Потом — 9%. Потом — 0.9%. Вот теперь дошла очередь и до 0.1%.

V>В C# ключевое слово const у мемберов объявляет аналог статического readonly поля объекта.




V>Тебе нужны были железобетонные гарантии — ну и вот.

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

V>Если же речь об алгоритмах и структурах данных — ну так пиши эти структуры и алгоритмы над ними.

V>Весь механизм для этого есть.


V>Это именно реализация некоторых иммутабельных алгоритмов над некоторыми иммутабельными структурами данных.


V>Collections.Immutable не приносит в язык иммутабельность как таковую, эта либа лишь реализует некоторые структуры и алгоритмы, доказавшие свою полезность в ФП.
По-видимому, вы так и не понимаете, что такое иммутабельность как таковая.


V>Я могу нарушать гарантии интерфейсов, например, реализовывать тип как мутабельный и возвращать this из методов.

Интерфейсов — да. Классов — нет.

S>>Вот мой метод:

S>>
S>>public void RegisterState(ImmutableDictionary<string, int> state)
S>>{
S>>  _visited[state] = true;
S>>}
S>>

S>>Попробуйте "сломать" его, передав в него мутабельный state.

V>У тебя ошибка, должно быть так:

Нет никакой ошибки. state — immutable, _visited — mutable.

V>И это у тебя оно гарантируется из-за использования конкретного типа ImmutableDictionary<,>, а если сделать так:

V>
V>public void RegisterState<TDict, TKey, TValue>(TDict state)
V>    where TDict : IImmutableDictionary<TKey, TValue> {...}
V>

V>то прощай гарантии, можно начинать хулиганить. ))
Ну, так поэтому я и не стал писать так, чтобы вы начинали хулиганить.

V>В плюсах, напротив, можно определить некий концепт ImmutableMap и юзать так:

V>
V>template<ImmutableMap TDict>
V>void RegisterState(TDict state) {
V>   visited_ = merge(visited_, state, true);
V>}
V>

V>Где концепт ImmutableMap требует, например, быть инстансом шаблонного типа ImmutableMapWrapper, которому в кач-ве аргумента подали тип, удовлетворяющий публичному интерфейсу std::map.
И чем это будет отличаться от конкретного ImmutableMapWrapper<std::map>?

V>Что касается ключевой фишки иммутабельных списков/графов, т.е. что касается шаренья узлов между инстансами, этого можно достичь или через счётчики ссылок, или через техники навроде региональной памяти, чтобы не возиться со счётчиками.

Эффективность упадёт на дно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[16]: понимание ООП Алана Кея
От: korvin_  
Дата: 29.03.23 09:24
Оценка:
Здравствуйте, Sinclair, Вы писали:


S>Методы гораздо удобнее выстраивать в цепочку. Запись вида list.add(1).add(2).add(42) читать гораздо комфортнее, чем add(add(add(list, 1), 2), 42))).

S>Но это дело вкуса. Покажите, как вы будете реализовывать внешний метод add.

Вариант №1:

let add x xs = List.append xs [x]
 
let () =
  let xs = [] in
  let xs = xs |> add 1 |> add 2 |> add 42 in
  printfn "%A\n" xs

=>

Вариант №2:

let (++) xs x = List.append xs [x]
 
let () =
  let xs = [9000] in
  let xs = xs ++ 1 ++ 2 ++ 42 in
  printfn "%A\n" xs


=>

[1; 2; 42]
Re[14]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 29.03.23 10:26
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Вдогонку, у дотнета уже были интерфейсы, описывающие АПИ иммутабельных коллекций, навроде IReadOnlySet и т.д.

S>Вы по-прежнему не понимаете смысл иммутабельности.

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


S>IReadOnlySet, IReadOnlyList и прочие никакого отношения к иммутабельности не имеют.


Имеют такое же отношение, как и IImutableList и Ко.
Это высказывание — вполне себе тезис.


V>>Выбранное название неймспейса сбивает с толку, бо там речь не об иммутабельности как таковой, а об эффективных способах порожения read-only коллекций. ))

S>Нет. Речь там именно об иммутабельности, а не о read-only.

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

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


V>>Например, неэффективной будет реализация на основе некоего FrozenSet, т.к. потребует копирования коллекций целиком при порождении новых коллекций, т.е. "изменения" их в ФП-стиле.

S>А то.

Что, однако, верно только для сценариев, где данные активно изменятся.
FrozenXXX были созданы для других сценариев — где данные читаются намного чаще, чем изменяются.

Для сравнения, есть структура-обертка ImmutableArray, в нём данные при изменении каждый раз копируются.
Собсно, до введения такого типа в стандартные либы, аналогичная обёртка была, наверно, в каждой конторе, которые разрабатывают под дотнет.
По смыслу поведение этого типа больше просится во Frozen-неймспес, бо та же стратегия... но уже реализовали ранее в Immutable, бгг...
У нас в core-библиотеке эта обертка называется ReadOnlyArray. В Unity тоже и вообще ты можешь найти многие сотни аналогичных реализаций в открытых проектах на дотнете.

Еще пара замечаний относительно FrozenXXX:
  • нет никаких требований копирования при реализации, ты можешь рядом делать свои реализации со своей стратегией;
  • методы-расширения, типа ToFrozenSet(), берут на входе неиммутабельные последовательности (или множества) и создают копии данных, т.е. в точности как в вслучае ToImmutableSet();
  • ImmutableSet<T> — это абстрактный класс, есть несколько его реализаций, все они иммутабельны by design;
  • идея Frozen-коллекций — это расположить данные в памяти таким образом, чтобы они на современной аппаратуре были эффективны для чтения.
    Например, реализация SmallFrozenSet хранит данные в линейном массиве и ищет их линейным поиском, а хеш-реализации имеют более эффективное представление корзин в памяти на тру-иммутабельных структурах Bucket { int StartIndex; int EndIndex; };
  • мы именно с тобой обсуждали N-арные деревья, т.е. ты должен хорошо понимать, что нет никакого запрета строить иммутабельные деревья из таких структур данных, сбалансированных с точностью от 1 до некоего выбранного max N узлов; т.е., при создании нового корня дерева в общем случае будут создаваться копии меньшего кол-ва узлов, чем для двоичных узлов, но в каждом узле будет больше элементов. До какого-то N это выгодная стратегия на современном оборудовании, потому что деревянная в памяти структура в общем случае обладает худшей локальностью, т.е. синтетические тесты, где в памяти только содаются узлы тестируемого дерева, могут сильно врать насчёт его эффективности.

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

    В любом случае, рассуждать об иммутабельности или нет немспейса Frozen было заведомо глупо — типы данных там точно так же иммутабельны by design, как и в неймспейсе Immutable.
    Это просто "еще одна реализации иммутабельности" для еще одного популярного сценария.

    Сравнить с тем же AST, где количество операций чтения обычно примерно равно кол-ву операций записи.
    Разумеется, для сценария построения и обработки AST выгодней брать иммутабельные графы, как оно есть в AST-графе того же Рослина — там AST описано на основе иммутабельных типов.
    Жаль, на момент разработки Рослина еще не было типов ImmutableList или ImmutableStack, поверх которых легко строить иммутабельные лиспоподобные графы, бгг.

    Однако, если по однажды построенной иммутабельной AST многократно работает некий "вычислитель" (допустим, стековая машинка), то выгодней представить это AST в линейном виде в памяти, собсно, как оно и есть в реализации Frozen-коллекций.
  • Отредактировано 29.03.2023 10:32 vdimas . Предыдущая версия .
    Re[17]: понимание ООП Алана Кея
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 29.03.23 10:33
    Оценка:
    Здравствуйте, korvin_, Вы писали:

    _>Вариант №1:


    _>
    _>let add x xs = List.append xs [x]
     
    _>let () =
    _>  let xs = [] in
    _>  let xs = xs |> add 1 |> add 2 |> add 42 in
    _>  printfn "%A\n" xs
    _>

    _>=>

    _>Вариант №2:


    _>
    _>let (++) xs x = List.append xs [x]
     
    _>let () =
    _>  let xs = [9000] in
    _>  let xs = xs ++ 1 ++ 2 ++ 42 in
    _>  printfn "%A\n" xs
    _>


    _>=>


    _>
    _>[1; 2; 42]
    _>

    Это какой язык? Сходу непонятно. Тут-то речь идёт о C++ и его волшебной const-магии.
    Когда у вас в язык встроен иммутабельный список с заранее оптимизированными add, то вопросов нет. Интересно становится, когда он не встроен.
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[15]: понимание ООП Алана Кея
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 29.03.23 10:43
    Оценка:
    Здравствуйте, vdimas, Вы писали:
    V>Всякий раз забавно видеть, как ты пытаешься примитивщину выдавать за откровения.
    V>Лучше бы ты не маялся этой хернёй, а говорил тезисами, которые можно подтвердить или опровергнуть.


    S>>IReadOnlySet, IReadOnlyList и прочие никакого отношения к иммутабельности не имеют.


    V>Имеют такое же отношение, как и IImutableList и Ко.

    V>Это высказывание — вполне себе тезис.
    Ну да, совершенно верно. Это — ошибочный тезис.
    Иммутабельность означает не просто отсутствие модифицирующих операций, но и наличие операций по построению новых значений на основе существующих.

    V>Я на это уже отвечал — там речь лишь о некоторых алгоритмах над некоторыми иммутабельныим структурами данных.

    Ткните пальцем хотя бы в один "алгоритм" в этом неймспейсе.

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

    А то.
    V>А система реализованных интерфейсов — нет, это просто "вербальные соглашения".
    Всё верно. Причём достаточно внятные вербальные соглашения. В принципе, любой интерфейс, помимо названий методов, оперирует некоторыми вербальным соглашениями. Нарушение которых компилятором не карается, но может привести к неожиданному поведению (в том числе и к багам).

    V>Например, интерфейс IImutableDictionary может быть реализован не только на красно-чёрных деревьях.

    А то. У нас с одногруппником в качестве курсовой работы была реализация IImmutableList и IImmutableDictionary на другой основе.
    V>И может быть реализован не только в иммутабельном виде.
    Может, но это всё сломает. А вот в read-only виде его реализовать эффективно очень затруднительно, если вообще возможно.

    V>Что, однако, верно только для сценариев, где данные активно изменятся.

    V>FrozenXXX были созданы для других сценариев — где данные читаются намного чаще, чем изменяются.
    Да, для специального узкого случая.
    Вы готовы навскидку сказать, насколько они эффективнее Immutable-аналогов?

    V>* мы именно с тобой обсуждали N-арные деревья, т.е. ты должен хорошо понимать, что нет никакого запрета строить иммутабельные деревья из таких структур данных, сбалансированных с точностью от 1 до некоего выбранного max N узлов; т.е., при создании нового корня дерева в общем случае будут создаваться копии меньшего кол-ва узлов, чем для двоичных узлов, но в каждом узле будет больше элементов. До какого-то N это выгодная стратегия на современном оборудовании, потому что деревянная в памяти структура в общем случае обладает худшей локальностью, т.е. синтетические тесты, где в памяти только содаются узлы тестируемого дерева, могут сильно врать насчёт его эффективности.


    Мы не просто это обсуждали. Я такую структуру реализовывал самостоятельно.

    V>N-арное дерево в этом смысле — это обеспеченный ручками некий фактор локальности данных. В идеале, конечно, было бы удобно иметь ср-ва располагать такие "корзины" по границам кеш-линееек проца, но дотнету такие радости недоступны.

    Прекрасно доступны, надо просто уметь их готовить.

    V>В любом случае, рассуждать об иммутабельности или нет немспейса Frozen было заведомо глупо — типы данных там точно так же иммутабельны by design, как и в неймспейсе Immutable.

    Тогда зачем вы притащили его обсуждение в эту ветку?
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Отредактировано 29.03.2023 10:44 Sinclair . Предыдущая версия .
    Re[16]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 29.03.23 12:07
    Оценка:
    Здравствуйте, Sinclair, Вы писали:

    S>Понятно. Вижу, вы исправили ошибку — теперь у вас в коде 2 move. Исходный код, который вы привели, падал на assert(s1 == "").


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

    В своё оправдание этой ужасной ошибки могу сказать лишь, что по сигнатуре ImmutableValue(T && value) должен был быть понятен замысел, т.к. rvalue && используется в плюсах для семантики перемещения.


    V>>Изменить законными способами значение переменной s2 нельзя;

    S>Осталось понять, что изменится, если мы поубираем ключевые слова const из вашего шаблонного кода. Перестанет ли isting быть иммутабельным?

    На очевидные вопросы я отвечал заранее:

    Ключевое слово const даст гарантии сильнее "вербальных соглашений", которые в реальной жизни не обеспечиваются ничем, кроме добросовестности программиста. ))
    Даже правильно спроектированный под конкретную задачу тип затем может быть поломан из-за невнимательности, при добавлении мутабельных методов.

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

    Уже два профита.
    (в дотнете вокруг readonly аналогично)


    V>>Последняя строка тоже важна для понимания — получив однажды ссылку на ImmutableValue<std::string> я могу быть уверен, что переменную не затрут.

    S>Вот этот фрагмент непонятен. Зачем вы производите копии иммутабельного объекта? Он же гарантированно иммутабельный, это как раз и даёт ссылочную прозрачность.

    На это тоже уже отвечал:

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

    Семантика типов-классов в С++ изначально value-type, ссылочная семантика добавляется явно при надобности.
    Создание копий иммутабельных данных может быть полезным для региональной памяти, когда необходимо сохранить данные в своём регионе.


    S>Эффективность упадёт на дно.


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

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

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


    V>>Для иммутабельных типов это будут внешние ф-ии и операторы, разумеется.

    S>Методы гораздо удобнее выстраивать в цепочку. Запись вида list.add(1).add(2).add(42) читать гораздо комфортнее, чем add(add(add(list, 1), 2), 42))).
    S>Но это дело вкуса. Покажите, как вы будете реализовывать внешний метод add.

    Я бы назвал его merge, и добавил бы сигнатуры добавления не только отдельных значений, но и готовых set.
    Что касается синтаксиса многократного добавления за раз — тоже не вижу проблем в условиях Arg... ср-в языка С++.

    Внешне это может выглядеть так:
    auto newSet = merge(oldSet, 1, 2, 3, anotherSet1, anotherSet2);

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

    Схематично так:
    template<typename T>
    class ImmutableSet {
        //...
    };
    
    template <typename T> 
    ImmutableSet<T> merge(ImmutableSet<T> set) {
        return set;
    }
    
    template <typename T> 
    ImmutableSet<T> merge(ImmutableSet<T> set, T value) {
        //...
    }
    
    template <typename T> 
    ImmutableSet<T> merge(ImmutableSet<T> set1, ImmutableSet<T> set2) {
        //...
    }
    
    template <typename T, typename THead, typename... TRest> 
    ImmutableSet<T> merge(ImmutableSet<T> set, THead head, TRest ...rest) {
        return merge(merge(set, head), rest...);
    }


    При вызове merge(oldSet, 1, 2, 3, anotherSet1, anotherSet2) код раскроется в
    merge(merge(merge(merge(merge(merge(oldSet, 1), 2), 3), anotherSet1), anotherSet2))

    merge — потому что такова природа сущности "множество". ))
    Уникальность и всё в таком роде.
    Для multiset чуть другая семантика, без уникальности, но merge тоже подходит по смыслу.


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


    Зачем учить?
    Это просто данная сверху бесплатная функциональность, бо компилятор порождает конструкторы копирования и перемещения автоматически (хотя их и можно переопределить).
    А семантику ты выбираешь уже исходя из сценариев и природы данных.
    Для случая активного изменения данных я выберу ссылочную семантику, для случая frozen-данных выберу семантику по-значению, разумеется.

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


    V>>copy-on-write для небольших объемов линейно-расположенных в памяти данных работает лучше иммутабельного графа.

    S>Началось виляние. Узнаю коллегу вдимаса.

    Чего? ))
    Я рядом давал тебе ссылку на исходники SmallFrozenSet.
    Это ж известная фишка — ортогональность семантики и подробности реализации.
    Для различных сценариев работы с данными одна и та же семантика может быть реализована различными стратегиями.


    V>>Но это мы уже ответвились в другую область — в область алгоритмов и структур данных, а они зависят от конкретных задач (или классов задач, бо практически все задачи уже известны, бгг).

    S>Конкретная задача хорошо известна — реализовать иммутабельные структуры так, чтобы они были а) надёжными, б) эффективными.

    Да не бывает никакой абстрактной эффективности.
    Бывает эффективность в конкретной задаче (на конкретных данных).

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


    V>>В моём случае не придётся для сценария, скажем, для которого разработали FrozenDictionary в 8-м дотнете.

    S>Да, для этого экзотического сценария можно сделать реализацию эффективнее, чем ImmutableDictionary. Обратите внимание на две вещи:
    S>1. Ваша реализация оказалась уместна только для экзотического частного случая; для более общего частного случая она непригодна.

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

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


    S>2. В дотнете как-то обошлись без магии ключевого слова const.


    Не обошлись, там точно такая же магия для структур в ключевом слове readonly.
    Но для классов этого модификатора нет, увы.

    А было бы неплохо и для интерфейсов-классов, где IImuttableList мог быть объявлен как readonly interface IImuttableList, соответственно, все реализующие его классы должны были бы быть readonly IImuttableList : ImuttableList {...}


    S>Выходит, любые виды неизменности реализуемы и без него. Сюрприз-сюрприз!


    А точно ли сюрприз? ))
    Разве нельзя расписать иммутабельность, скажем, на ассемблере? — конечно можно.
    Но ты просил гарантии, поэтому я и продемонстрировал ср-ва для выражения гарантий.

    Например, новомодные лямбды в С++ иммутабельны уже по-умолчанию (что позволяет эффективно их инлайнить), но при надобности описать мутабельную лямбду, её надо объявлять с ключевым словом mutable.


    V>>И странно, что оно не было сделано еще лет 20 назад, когда тема иммутабельных деревьев была модной на слуху.

    S>Сначала удовлетворяются потребности 90% аудитории. Потом — 9%. Потом — 0.9%. Вот теперь дошла очередь и до 0.1%.

    Да не уверен, если честно, бо 90% всё-равно пользуются тем, что дано изкаробки.
    Будь подобные типы изначально в коробке, ими пользовались более 0.1%, просто потому что "оно уже есть".
    И многие идущие в поставке библиотеки тоже могли бы пользовать immutable-типы с гораздо большим процентом.

    Думаю, твои оценки потенциальной популярности не верны для платформы с GC, бо именно на такой платформе иммутабельность достаточно дешевое удовольствие.
    А значит — полезное! ))


    V>>Тебе нужны были железобетонные гарантии — ну и вот.

    S>Так гарантии по-прежнему обеспечены дизайном класса, а не ключевым словом const.

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


    V>>Collections.Immutable не приносит в язык иммутабельность как таковую, эта либа лишь реализует некоторые структуры и алгоритмы, доказавшие свою полезность в ФП.

    S>По-видимому, вы так и не понимаете, что такое иммутабельность как таковая.

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


    V>>Я могу нарушать гарантии интерфейсов, например, реализовывать тип как мутабельный и возвращать this из методов.

    S>Интерфейсов — да. Классов — нет.

    Хорошо, что согласился хоть с чем-то. ))
    Проблематика заданного именно тобой обсуждения как раз в интерфейсах — ты хотел писать обобщённые алгоритмы в условиях гарантий.

    Увы, увы, на дотнете этого пока невозможно.
    Но я не утверждаю, что этого будет невозможно и в будущем.
    Таки, введения readonly interface и readonly class потребует совсем косметических доработок платформы...
    Глядишь, может когда и сделают.


    V>>У тебя ошибка, должно быть так:

    S>Нет никакой ошибки. state — immutable, _visited — mutable.

    Тю, неинтересно. ))
    Оно и так в реальной жизни отродясь иммутабельно для State и до введения готовых Immutable-реализаций.
    Если бы тебе приходилось упражняться в написании лексеров и парсеров (и вообще ДКА), ты бы это знал на уровне мозжечка и так.


    V>>И это у тебя оно гарантируется из-за использования конкретного типа ImmutableDictionary<,>, а если сделать так:

    V>>то прощай гарантии, можно начинать хулиганить.
    S>Ну, так поэтому я и не стал писать так, чтобы вы начинали хулиганить.

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

    Смотри, как одни компромиссы тянут за собой другие. ))


    V>>В плюсах, напротив, можно определить некий концепт ImmutableMap и юзать так:

    V>>
    V>>template<ImmutableMap TDict>
    V>>void RegisterState(TDict state) {
    V>>   visited_ = merge(visited_, state, true);
    V>>}
    V>>

    V>>Где концепт ImmutableMap требует, например, быть инстансом шаблонного типа ImmutableMapWrapper, которому в кач-ве аргумента подали тип, удовлетворяющий публичному интерфейсу std::map.
    S>И чем это будет отличаться от конкретного ImmutableMapWrapper<std::map>?

    Э-э-э... я тут столько распинаюсь...
    В общем, будет отличаться тем, бо абстракция ImmutableMapWrapper бесплатна, а обёрнут может быть не только конкретный std::map.
    Ну и, из-за наличия частичного определения шаблонов, может быть обёрнут даже тип, который не совместим по публичному интерфейсу в std::map.

    В общем, у плюсов достаточно своих плюшек в деле расписания абстракций, это не только "машинно-эффективный" язык, это еще уровень абстрагирования, сравнимый с мощными ФП-языками.
    На одной эффективности С++ так не выехал бы, разумеется, бо в период набора им популярности существовали сравнимые по эффективности языки, тот же Object Pascal от Борланд.

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

    А как тебе arg... — семантика?
    Статически делается то, что в ФП-языках отродясь делалось динамически, хотя потенциальная возможность перевести в статику есть, просто современные машины слишком слабы для выполнения оптимизирующим компилятором редукций ФП-кода "до самого дна".

    В этом смысле С++ "приближен к реальности" не только из-за "близости языка к машине", но решения в дизайне языка пляшут от возможностей компиляторов на современной технике.
    Компромиссы, компромиссы.. всегда они...
    Re[18]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 29.03.23 12:31
    Оценка:
    Здравствуйте, Sinclair, Вы писали:

    S>Это какой язык? Сходу непонятно.


    Какой-нить ML-семейства с возможностью определения операторов.


    S>Тут-то речь идёт о C++ и его волшебной const-магии.


    Чёт уже в голос...
    Сколько я с тобой обсуждал этот несчастный const — ты его то недооцениваешь, то переоцениваешь.

    Ключевое слово const — оно не само в себе весчь, в разных синтаксических конструкциях имеет разную семантику.
    Соотв., обсуждать можно только конкретные синтаксические конструкции, где допустимо это ключевое слово.
    Предлагаю впредь поступать именно так, дабы не испытывать терпение коллег-читателей.


    S>Когда у вас в язык встроен иммутабельный список с заранее оптимизированными add, то вопросов нет. Интересно становится, когда он не встроен.


    В примере акцент не на списке (и откуда ты взял про "эффективность?"), а на внешний вид выражений.

    В плюсах тоже можно описать некий operator|(Set, Set), чтобы выглядело примерно так:
    auto newSet = set1 | set2 | value3 | value4;

    В дотнете, кстате, тоже.
    Я бы еще определил для множеств операторы &, -, ^ (исключающее ИЛИ, симметричная разность), \ (дополнение), * (декартово произведение), <, >, ==, !=, <=, >=.

    ======================
    Ну и, согласно семантики, для multiset я бы выбрал operator+(Set, Set) для выражения операции объединения мн-в вместо логического оператора |.
    Отредактировано 29.03.2023 15:10 vdimas . Предыдущая версия .
    Re[17]: понимание ООП Алана Кея
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 29.03.23 13:01
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>На очевидные вопросы я отвечал заранее:

    V>

    V>Ключевое слово const даст гарантии сильнее "вербальных соглашений", которые в реальной жизни не обеспечиваются ничем, кроме добросовестности программиста. ))
    V>Даже правильно спроектированный под конкретную задачу тип затем может быть поломан из-за невнимательности, при добавлении мутабельных методов.

    Эти слова я читал. Я не понимаю, как вы будете их применять. Что именно помешает "по невнимательности" добавить в ImmutableValue<T> мутирующих методов?

    V>С одной стороны, это не более чем эксплуатация выразительных ср-в языка, что мы гарантии прописываем через синтаксис (а не комментарии) и контроллируем их через систему типов.

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

    V>Счётчики обычно дергаются при перестроении, допустим, графа на иммутабельных узлах, поэтому доп. накладные расходы не настолько уж велики, чтобы говорить о дне, бо перестраивается только часть узлов.

    Счётчики ссылок, очевидно, дёргаются при любом копировании. Как раз "перестроенные" узлы, которых мало, получают счётчики == 1.

    V>А региональная память — это когда некая подсистема сама распределяет память на низком уровне, обращаясь к операционке только за относительно большими страницами памяти.

    V>В таких распределителях память обычно выделяется как grow-only, простым смещением текущего указателя свободной области, т.е. выделение происходит с эффективностью дотнета.
    V>А освобождается затем и вовсе бесплатно, целиком одной операцией возврата операционке страницы (страниц).
    Это если у вас хватает размера региона на полную работу алгоритма. Заранее сказать, сколько потребуется памяти для хранения мусора, порождаемого компилятором при анализе AST, весьма затруднительно.
    А как только вы исчерпали регион — добро пожаловать в честное копирование.
    V>В целом оно на порядки эффективнее, чем в дотнете.
    "В целом" — вряд ли. В частных случаях — может быть. То, что вы называете "региональной памятью", другие люди называют ареной, и это, в целом, аналог поколений в современных GC, только на ручном приводе.
    И применяется, как правило, там, где потребности в памяти можно предсказать — либо мало кросс-ссылок между данными. Какой-нибудь видео и аудиопроцессинг с этим будет работать хорошо, а вот компиляторы и прочие трансформации графов — вряд ли.

    V>Я бы назвал его merge, и добавил бы сигнатуры добавления не только отдельных значений, но и готовых set.

    Да я вас не про сигнатуру спрашиваю. Сигнатура — штука очевидная.

    V>merge — потому что такова природа сущности "множество". ))

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

    V>Для случая активного изменения данных я выберу ссылочную семантику, для случая frozen-данных выберу семантику по-значению, разумеется.

    Просто при передаче вашего ImmutableValue() по значению, будет создаваться deep-копия вложенных в него данных. Со стоимостью O(N).

    V>Чего? ))

    V>Я рядом давал тебе ссылку на исходники SmallFrozenSet.
    Да при чём тут ссылка? Вы просто опять уходите в решения для каких-то экзотических частных случаев.

    V>Ну и, не забыть провести сравнительные тестирования. ))



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

    Я говорю конкретно про иммутабельность. Много ли readonly вы видите в System.Collections.Immutable?
    V>Но для классов этого модификатора нет, увы.

    V>А было бы неплохо и для интерфейсов-классов, где IImuttableList мог быть объявлен как readonly interface IImuttableList, соответственно, все реализующие его классы должны были бы быть readonly IImuttableList : ImuttableList {...}

    Это — бесполезная идея. Она одновременно не даст никаких гарантий иммутабельности (потому, что внутри реализации ImmutableList — ссылка, которая, в том числе, может быть и на mutable данные),
    и помешает некоторым сценариям, где иммутабл-объект изменяется в процессе конструирования.

    V>Но ты просил гарантии, поэтому я и продемонстрировал ср-ва для выражения гарантий.

    Повторюсь: гарантии в продемонстрированном вами подходе никак не зависят от применения ключевого слова const.
    Если вы его уберёте, ImmutableValue<> останется ровно на столько же иммутабельным, как и был.

    V>Пусть по-мелочам больше думает машина, чем программист.

    Непонятно, что вы имеете в виду. Я всё ещё не вижу сценария, который вы предотвращаете при помощи этого ключевого слова.

    V>Хорошо, что согласился хоть с чем-то. ))

    V>Проблематика заданного именно тобой обсуждения как раз в интерфейсах — ты хотел писать обобщённые алгоритмы в условиях гарантий.
    Разве? Может, я что-то подзабыл. Можно указать, где именно я настаивал на интерфейсах?

    V>Оно и так в реальной жизни отродясь иммутабельно для State и до введения готовых Immutable-реализаций.

    "Оно" — это кто?

    V>Э-э-э... я тут столько распинаюсь...

    V>В общем, будет отличаться тем, бо абстракция ImmutableMapWrapper бесплатна, а обёрнут может быть не только конкретный std::map.
    V>Ну и, из-за наличия частичного определения шаблонов, может быть обёрнут даже тип, который не совместим по публичному интерфейсу в std::map.
    Как только вы обернёте не только конкретный sdd::map, появится и возможность "хулиганить".

    V>А как тебе arg... — семантика?

    У меня со времён params в С# такой синтаксис удивления не вызывает. А семантика, на первый взгляд, слабже — не так удобно извлекать коллекцию, и принудительная замена итерации рекурсией не выглядит шибко привлекательно. Вот если бы ещё шарп починили, разрешив приписывать params к параметру любого типа, который реализован массивом. В частности, IEnumerable<T> и IList<T>.
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[19]: понимание ООП Алана Кея
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 29.03.23 13:06
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, Sinclair, Вы писали:


    S>>Это какой язык? Сходу непонятно.


    V>Какой-нить ML-семейства с возможностью определения операторов.



    S>>Тут-то речь идёт о C++ и его волшебной const-магии.


    V>Чёт уже в голос...


    V>Ключевое слово const — оно не само в себе весчь, в разных синтаксических конструкциях имеет разную семантику.

    V>Соотв., обсуждать можно только конкретные синтаксические конструкции, где допустимо это ключевое слово.
    V>Предлагаю впредь поступать именно так, дабы не испытывать терпение коллег-читателей.
    Начните с себя. Вот ваша цитата:

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

    Что ж вы тут обсуждаете не конкретные синтаксические конструкции, а испытываете терпение коллег-читателей?

    V>В примере акцент не на списке (и откуда ты взял про "эффективность?"), а на внешний вид выражений.

    А зачем мне внешний вид выражений? Внешний вид я и сам какой угодно придумаю.
    Вопрос в реализации — будет ли у вас там O(N) копирование на каждый чих, и будет ли добавление K элементов стоить O(N*K).
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[18]: понимание ООП Алана Кея
    От: korvin_  
    Дата: 29.03.23 13:19
    Оценка: +1
    Здравствуйте, Sinclair, Вы писали:

    S>Это какой язык? Сходу непонятно.


    F#

    S>Тут-то речь идёт о C++ и его волшебной const-магии.


    Я отвечал на «удобство построения цепочек вызовов».
    Re[16]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 29.03.23 15:02
    Оценка: :)
    Здравствуйте, Sinclair, Вы писали:

    S>>>IReadOnlySet, IReadOnlyList и прочие никакого отношения к иммутабельности не имеют.

    V>>Имеют такое же отношение, как и IImutableList и Ко.
    V>>Это высказывание — вполне себе тезис.
    S>Ну да, совершенно верно. Это — ошибочный тезис.
    S>Иммутабельность означает не просто отсутствие модифицирующих операций, но и наличие операций по построению новых значений на основе существующих.

    Ну что опять за детсад, мистер коллега с большим стажем?
    Речь про встроенные в язык "операции" или библиотечного уровня?
    Если второе (а оно второе), то тезис перестаёт быть ошибочным, т.к. есть возможность написать аналогичную функциональность над готовыми read-only-design типами с публичными конструкторами.
    (или защищёнными такими конструкторами... облом расписывать всю комбинаторику достаточных условий)


    V>>Я на это уже отвечал — там речь лишь о некоторых алгоритмах над некоторыми иммутабельныим структурами данных.

    S>Ткните пальцем хотя бы в один "алгоритм" в этом неймспейсе.

    Например, алгоритм балансировки RB-дерева внутри ImmutableSet и ImmutableDictionary.
    Если мне нужны другие алгоритмы, берутся интерфейсы IImmutableSet и IImmutableDictionary, и делаются другие реализации.

    Поэтому, абстрактный код над иммутабельными типами данных стоит писать на основе интерфейсов (ограничений в интерфейсах, в т.ч. для value-type реализаций, которые имеют смысл фасада над внутренними ссылочными узлами дерева), а не конкретных классов.
    И тут мы возвращаемся в самое начало — для интерфейсов никакой иммутабельной реализации не гарантируется.
    Круг замкнулся. ))


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

    S>А то.

    А то, что это убого в рассуждениях — нельзя подменять иммутабельность как таковую на достаточно узкую конкретную реализацию.
    А потом имеешь нахальство утверждать, что окружающие "не понимают иммутабельности".
    Окружающие всё понимают и потихоньку ржут или недоумевают.

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


    V>>А система реализованных интерфейсов — нет, это просто "вербальные соглашения".

    S>Всё верно. Причём достаточно внятные вербальные соглашения.

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


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


    Разумеется.
    Но "железная" гарантия иммутабельности даёт возможность одновременного доступа к данным без блокировок, а это уже не семантическая потенциальная ошибка, а вполне себе хардкорная техническая, которая, будучи совершённой, приводит к плохообнаруживаемым достаточно серьёзным проблемам.


    V>>Например, интерфейс IImutableDictionary может быть реализован не только на красно-чёрных деревьях.

    S>А то. У нас с одногруппником в качестве курсовой работы была реализация IImmutableList и IImmutableDictionary на другой основе.

    Т.е. понимаешь аргументы оппонента, но всё-равно споришь. ))


    V>>И может быть реализован не только в иммутабельном виде.

    S>Может, но это всё сломает.

    При гарантиях read-only не сломает при одновременном доступе.
    Тут опять дихотомия необходимости и достаточности.

    Read-only — одна из необходимостей для описанного выше.
    Еще необходимо совместное владение данными из разных потоков с контролем времени жизи.

    Immutable+GC — достаточность, автоматом покрывает необходимости read-only + совместного владения в конкретном сценарии.
    Но это не единственная связка, образующая достаточность, ес-но.
    И встроенных immutable-гарантий для интерфейсов и абстрактных классов в дотнете нет, увы.


    S>А вот в read-only виде его реализовать эффективно очень затруднительно, если вообще возможно.


    Ой, блин...
    Все типы неймспейса Immutable — они read-only by design.
    Примерно как String, где Substring или operator+ порождают другой экземпляр.

    Одно синоним другому, как и константность ("постоянство" по-русски).


    V>>Что, однако, верно только для сценариев, где данные активно изменятся.

    V>>FrozenXXX были созданы для других сценариев — где данные читаются намного чаще, чем изменяются.
    S>Да, для специального узкого случая.

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


    S>Вы готовы навскидку сказать, насколько они эффективнее Immutable-аналогов?


    От данных зависит.
    Разве ты не знаешь оценки сложности сверху сбалансированного дерева и хеш-таблицы?
    Да и при оценке снизу затраты на линейный поиск на небольшой выборке в 2-3 раза меньше, чем на хеш-таблицу или обход графа таких же размеров.


    V>>* мы именно с тобой обсуждали N-арные деревья, т.е. ты должен хорошо понимать, что нет никакого запрета строить иммутабельные деревья из таких структур данных, сбалансированных с точностью от 1 до некоего выбранного max N узлов; т.е., при создании нового корня дерева в общем случае будут создаваться копии меньшего кол-ва узлов, чем для двоичных узлов, но в каждом узле будет больше элементов. До какого-то N это выгодная стратегия на современном оборудовании, потому что деревянная в памяти структура в общем случае обладает худшей локальностью, т.е. синтетические тесты, где в памяти только содаются узлы тестируемого дерева, могут сильно врать насчёт его эффективности.

    S>Мы не просто это обсуждали. Я такую структуру реализовывал самостоятельно.

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


    V>>N-арное дерево в этом смысле — это обеспеченный ручками некий фактор локальности данных. В идеале, конечно, было бы удобно иметь ср-ва располагать такие "корзины" по границам кеш-линееек проца, но дотнету такие радости недоступны.

    S>Прекрасно доступны, надо просто уметь их готовить.

    Они доступны только через unmanaged, но это тогда ср-ва чуть ли не чистого Си, включенного в C# как подмножество.
    И ты не сможешь эффективно ссылаться из таких структур на GC-типы, только через GC-handle, а это тормоза, т.е. профанация изначальной идеи сбацать реализацию пошустрее.


    V>>В любом случае, рассуждать об иммутабельности или нет немспейса Frozen было заведомо глупо — типы данных там точно так же иммутабельны by design, как и в неймспейсе Immutable.

    S>Тогда зачем вы притащили его обсуждение в эту ветку?

    Обиделся? ))
    А зачем ты притащил Immutable?

    Я притащил для демонстрации, конечно.
    Демонстрации того, что парадигма не равна конкретной реализации.
    Конкретная реализация может придерживаться некоей стратегии, исходя из знаний (предположений) о характере данных и способах оперирования ими.
    Отредактировано 29.03.2023 15:07 vdimas . Предыдущая версия . Еще …
    Отредактировано 29.03.2023 15:06 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 15:05 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 15:04 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 15:04 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 15:03 vdimas . Предыдущая версия .
    Re[20]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 29.03.23 15:29
    Оценка:
    Здравствуйте, Sinclair, Вы писали:

    V>>Ключевое слово const — оно не само в себе весчь, в разных синтаксических конструкциях имеет разную семантику.

    V>>Соотв., обсуждать можно только конкретные синтаксические конструкции, где допустимо это ключевое слово.
    V>>Предлагаю впредь поступать именно так, дабы не испытывать терпение коллег-читателей.
    S>Начните с себя. Вот ваша цитата:
    S>

    S>Через const в С++ можно выразить абсолютно все сценарии вокруг иммутабельности целиком, но это будет лишь небольшая вершинка айсберга всех сценариев, покрываемых const.

    S>Что ж вы тут обсуждаете не конкретные синтаксические конструкции, а испытываете терпение коллег-читателей?

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

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


    V>>В примере акцент не на списке (и откуда ты взял про "эффективность?"), а на внешний вид выражений.

    S>А зачем мне внешний вид выражений? Внешний вид я и сам какой угодно придумаю.

    Это был ответ на твоё

    Методы гораздо удобнее выстраивать в цепочку. Запись вида ... читать гораздо комфортнее, чем ...

    Для цепочки бинарных операций удобнее всего использовать бинарные же синтаксические операции, разумеется.
    Особенно если удачно их подобрать, с учётом встроенного в язык приоритета операций. ))


    S>Вопрос в реализации — будет ли у вас там O(N) копирование на каждый чих, и будет ли добавление K элементов стоить O(N*K).


    Будет зависеть от реализации.
    Для сбалансированного дерева будет O(log N), для иммутабельной хеш-таблицы близко к O(N/K), где K — отношение кол-ва элементов и уникальных хеш-кодов по модулю N.

    Кстате, пример хеш-таблиц как раз для случая, когда контейнер намного эффективнее для чтения, чем для изменений.
    Просто в дотнете хеш-таблицы недоделанные, вызывают удивление.
    По классике при перестроении хеш-таблицы необходимо подбирать гаммирующую функцию для минимизации конфликтов хеш-кодов, соотв. алгоритмы в теории есть.
    В дотнете эта стадия опущена.
    Т.е., дотнетная хеш-таблица — "компромисс на все случаи жизни", бгг...

    Кстате, надо будет взглянуть на FrozenHashTable, что там происходит в процессе построения...
    Re[18]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 29.03.23 20:00
    Оценка:
    Здравствуйте, Sinclair, Вы писали:

    S>Эти слова я читал. Я не понимаю, как вы будете их применять. Что именно помешает "по невнимательности" добавить в ImmutableValue<T> мутирующих методов?


    Если поля описаны как const, то мутировать их никак.
    Есть еще ключевое слово mutable — это когда к полю можно обращаться из const this метода.

    Например, интрузивные счётчики ссылок часто описывают как mutable, потому что они работают над совместным владением объектами, без вмешательства в потенциально read-only семантику прикладных данных. Чаще всего такие счётчики размещают как приватные в неких базовых классах, т.е. эти счётчики недоступны затем наследуемому "прикладному" коду, предоставляя, однако, через функциональность этих базовых классов требуемую инфраструктуру по ссылочному шаренью целевых данных.


    V>>С одной стороны, это не более чем эксплуатация выразительных ср-в языка, что мы гарантии прописываем через синтаксис (а не комментарии) и контроллируем их через систему типов.

    V>>Создание копий иммутабельных данных может быть полезным для региональной памяти, когда необходимо сохранить данные в своём регионе.
    S>Это опять какие-то вырожденные экзотические сценарии, нужные примерно никогда.

    Коль мы возимся с иммутабельностью эффективности ради, то "примерно никогда" на практике трасформируется в "часто".
    Серебрянной пули для эффективности нет, сие аксиома. ))
    В каждом конкретном случае используется комплекс мер для обеспечения эффективности этого случая, коль эффективность вообще требуется.


    V>>Счётчики обычно дергаются при перестроении, допустим, графа на иммутабельных узлах, поэтому доп. накладные расходы не настолько уж велики, чтобы говорить о дне, бо перестраивается только часть узлов.

    S>Счётчики ссылок, очевидно, дёргаются при любом копировании.

    Дергаются не у всего дерева, а только у непосредственных детей вновь создаваемых узлов при перестроении, а это log N.
    Т.е. еще зависит от вида дерева — RB, AVL или еще какие с арностью более 2 — тогда будет еще меньше дерганий счётчиков ссылок.

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


    S>Как раз "перестроенные" узлы, которых мало, получают счётчики == 1.


    У непосредственных детей новых узлов надо увеличить счётчик ссылок — мы же шарим имеющиеся узлы, не перестраиваемые при повороте.

    И опять же, смотря как реализовано дерево. Ссылочные ноды RB-деревьев могут хранить value-кластер тройки узлов {R, B, R}, т.е. в общем случае меньше дерганий счетчиков ссылок (вдвое меньше "ссылочная" высота дерева), как и для более арных деревьев.


    V>>А региональная память — это когда некая подсистема сама распределяет память на низком уровне, обращаясь к операционке только за относительно большими страницами памяти.

    V>>В таких распределителях память обычно выделяется как grow-only, простым смещением текущего указателя свободной области, т.е. выделение происходит с эффективностью дотнета.
    V>>А освобождается затем и вовсе бесплатно, целиком одной операцией возврата операционке страницы (страниц).
    S>Это если у вас хватает размера региона на полную работу алгоритма.

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

    Эти техники, считай, обязательные в реализациях нейтивных веб-серверов, баз данных, компиляторов и т.д., бо региоальная память хорошо себя показывает в сценариях "выделить память для отдельной операции и всю её освободить по окончании операции". Боюсь даже представить, как работали бы БД или компиляторы без региональной памяти, на обычном malloc на каждый чих. ))

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


    S>Заранее сказать, сколько потребуется памяти для хранения мусора, порождаемого компилятором при анализе AST, весьма затруднительно.


    А оно и не требуется.
    Каждая трансформация — отдельная операция, т.е. для временных данных можно юзать такие аллокаторы, длительностью жизни в операцию.
    Потом у аллокатора вызывается некий reset и он переиспользуется для другой операции и т.д.

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

    Это ж азы, а не "уникальность".
    Это сегодня мы разбалованы современными высокоуровневыми подходами...
    В более хардокорном прошлом это всё из разряда не уникальности, а must have, то бишь, не новинка, а наоборот, традиции.


    S>А как только вы исчерпали регион — добро пожаловать в честное копирование.


    Регион — это несколько страниц.

    Вот у тебя тройка указателей:
    void * page_end;
    void * cursor;
    void * page_begin;

    Курсор просто ползёт до page_end, после этого выделяется новая страница, а на старую ссылаются в первом слове новой страницы.
    Соответственно, у первой выделенной страницы первое слово всегда null — конец списка.


    V>>В целом оно на порядки эффективнее, чем в дотнете.

    S>"В целом" — вряд ли. В частных случаях — может быть.

    В целом — это сам этот подход.
    Просто можно и его испортить, разумеется. ))

    Например, в дотнете аллокатор расположен в TLS-слоте (что тоже далеко не верх эффективности — просто получить ссылку на аллокатор, у которого попросить память).
    В твоей же реализации могут быть разные подходы — тоже на TLS или некий передаваемый в операции явно context (а в нём даже несколько таких аллокаторов для нескольких подсистем) и т.д.

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


    S>То, что вы называете "региональной памятью", другие люди называют ареной


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


    S>и это, в целом, аналог поколений в современных GC, только на ручном приводе.


    Не скажи...
    Например, нулевое поколение — это просто список объектов с некоей "головы".
    Затем уплотнения может и не быть (не было неявных освобождений), но голову по окончании цикла GC переназначат.

    Сравнивать эти подходы сложновато, потому что в их основе лежат принципиально разные стратегии из-за принципиально разных принятых допущений.
    "Просто другая стратегия", всё.

    Не обязательно пытаться найти ассоциативную связь со знакомым материалом. ))
    За одно только сязывание выделяемых объектов в списки в GC-системах убить мало. ))


    S>И применяется, как правило, там, где потребности в памяти можно предсказать — либо мало кросс-ссылок между данными.


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

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

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


    S>Какой-нибудь видео и аудиопроцессинг с этим будет работать хорошо, а вот компиляторы и прочие трансформации графов — вряд ли.


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

    Видео и аудио-процессинги мимо — там предвыделяются буфера для пайплайна алгоритма, никаких аллокаций/освобождений памяти в процессе работы кодеков быть не может, конечно.
    Память аллоцируется только в момент создания объекта-кодека.


    V>>merge — потому что такова природа сущности "множество". ))

    S>Вы распишите, что у вас там вместо // .... Поскольку у вас под капотом — заранее неизвестный мутабельный тип, то всё, что вы можете — это состряпать его копию, к которой потом будет применена манипуляция с изменением.

    И чем плоха копия такого объекта:
    class ImmutableSet<T> {
       const RBNode<T> * root_;   
    };

    где ImmutableSet<T> используется с семантикой значения, в данном случае значения-ссылки.

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

    Так для чего ж тогда ImmutableSortedSet<> объявлен ссылочным типом?
    А, ну да, ну да...
    — из-за встроенного дефолтного конструктора value-типов, который хоть уже и можно переопределить, но он вызывается не всегда.
    Пусть в своём коде я могу обложиться Debug.Assert() и требовать, чтобы был вызван недефолтный конструктор или статический метод-фабрика, но в библиотеке платфрменного уровня таких дыр быть не должно, она должна быть защищена от дурака.

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


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


    Позволяют что угодно, хосподя.
    Как напишешь — так и будет.

    Мой схематичный подход через "обертки" позволяет гарантировать иммутабельность для изначально описанных мутабельных типов.
    Например, узел Node может быть описан мутабельным, но если мы создаём эти узлы внутри реализации и помещаем в константную ссылку/указатель, то тип становится readonly.

    Т.е., реализация builder-а не требует дополнительных флагов, типа frozen (как в дотнетной реализации Immutable-узлов, и это почему появилсь отдельные read-only Frozen типы, где не требуется управлять еще и этими флагами). Достаточно по окончании построения присвоить граф константной ссылке/указателю (как говорил с самого начала).

    В вашем подопечном-защищаемом дотнетном Immutable совсем всё смешно — набили дерево мильоном узлов в билдере, а потом сказали ему Freeze() — и оно пошло честной стековой рекурсией обходить дерево для установки флага.
    "Не об этом я мечтала, когда замуж выходила..." (С) ))


    V>>Для случая активного изменения данных я выберу ссылочную семантику, для случая frozen-данных выберу семантику по-значению, разумеется.

    S>Просто при передаче вашего ImmutableValue() по значению, будет создаваться deep-копия вложенных в него данных. Со стоимостью O(N).

    Deep-копия автоматом только для плоской памяти.
    По указателю если — это надо расписывать ручками.
    Если используется интрузивный счётчик ссылок и иммутабельность, то копирование вызовет лишь +1 у счётчика cсылок корня:
    Рыба такая:
    template<typename T>
    class SharedHolder {
      intrusive_ptr<T> ptr_;
    
    public:
      T * get() {
        return ptr_.get();  
      }
    
      const T * get() const {
        return ptr_.get();  
      }
    };
    
    template<typename T>
    class Node;
    
    template<typename T>
    using NodeRef = SharedHolder<Node<T>>;
    
    template<typename T>
    class Node {
        T value;
        NodeRef<T> left_, right_;
        
    public:    
        Node * left() { return left_.get(); }
        const Node * left() const { return left_.get(); }
    
        Node * right() { return right_.get(); }
        const Node * right() const { return right_.get(); }
    };
    
    template<typename T>
    class AvlTreeSet {
        NodeRef<T> root_; 
    
    public:
        typedef T value_type;
    
        void insert(const T & value) {
           root_ = mutable_merge(root_, value);
        }
       
        AvlTreeSet<T> insert(const T & value) const {
           return immutable_merge(root_, value);
        }
    };
    
    template<typename TSet>
    class ImmutableSet {
        const TSet set_; 
        
        explicit ImmutableSet(TSet && s) : set_(s) {}
       
    public:
        ImmutableSet() {}
        
        const TSet & set() const {
           return set_;
        }
       
        ImmutableSet<TSet> operator|(const typename TSet::value_type & b) const {
           return ImmutableSet<TSet>(move(set_.insert(b)));
        }
    
        ImmutableSet<TSet> operator|(const ImmutableSet<TSet> & b) const {
           TSet tmp = set_;
            
           b.for_each([&](const auto & value) {
              tmp = static_cast<const TSet &>(tmp).insert(value);
           });
            
           return ImmutableSet<TSet>(move(tmp));
        }
    };
    
    int main()
    {
        using Set = ImmutableSet<AvlTreeSet<int>>;
        auto s1 = Set() | 1 | 2 | 3;
    
        return 0;
    }



    V>>Я рядом давал тебе ссылку на исходники SmallFrozenSet.

    S>Да при чём тут ссылка? Вы просто опять уходите в решения для каких-то экзотических частных случаев.

    Наоборот, говорил, что в плюсах есть возможность писать более обще, потому что у меня есть возможность распространять гарантии.
    Обрати внимание на сигнатуры парами:
      T * get() {
      const T * get() const {
    
      Node * left() { 
      const Node * left() const {
    
      Node * right() {
      const Node * right() const {


    По константной ссылке-указателю может быть вызвана только const-this версия метода, соответственно, у меня одно описание на все случаи жизни.
    См. тут:
        void insert(const T & value) {
           root_ = mutable_merge(root_, value);
        }
       
        AvlTreeSet<T> insert(const T & value) const {
           return immutable_merge(root_, value);
        }


    Константное поле у меня лишь в декораторе ImmutableSet<TSet>, далее констрантность распространяется.
    Что я могу себе позволить, чтобы мои абстракции не протекли:
    — дефолтный конструктор ImmutableSet (в примере auto empty = ImmutableSet<AvlTreeSet<int>>())
    — конструктор от значений или других ImmutableSet.

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

    Ф-ии навроде mutable_merge или immetable_merge могут быть внешними. При непосредственном вызове этих ф-ий над иммутабельными типами ничего гарантировать нельзя, разумеется.
    Наверно, тебя именно это беспокоило несколько лет назад и в этом обсуждении снова.
    Я же тебе говорил, что я могу распространить гарантии так, что они не протекут.
    На самый худой конец их можно распространить на самое дно — поля целевых типов. ))

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

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


    В целях эффективности класс ImmutableSet может содержать вложенный класс Builder, который манипулирует мутабельным TSet на стадии напихивания данных, и в какой-то момент передаёт владение набором данных в иммутабельный ImmutableSet. Т.е. классу Builder будет доступен приватный конструктор декоратора ImmutableSet, а тебе нет. И что забавно, всего одна реализация ImmutableSet и Builder на все реализации set с нужными наборами методов.


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

    S>Я говорю конкретно про иммутабельность. Много ли readonly вы видите в System.Collections.Immutable?

    Я там вообще мало чего интересного вижу и вряд ли когда буду пользоваться этой неэффективной реализацией.
    В упор не понимаю, чем тебя заинтересовало это убожество.
    Это ж для хомячков.
    "Чтобы было!" (С)


    V>>А было бы неплохо и для интерфейсов-классов, где IImuttableList мог быть объявлен как readonly interface IImuttableList, соответственно, все реализующие его классы должны были бы быть readonly IImuttableList : ImuttableList {...}

    S>Это — бесполезная идея.

    "Это потому что ты не понимаешь иммутабельности" (С)
    Для структур оказалась крайне полезной, потому что иначе возникал диссонанс с readonly-полями value-типов.

    Пришлось ввести readonly-методы, что есть полный аналог const-this методов в С++.


    S>Она одновременно не даст никаких гарантий иммутабельности


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

    S>потому, что внутри реализации ImmutableList — ссылка, которая, в том числе, может быть и на mutable данные


    Хосподя, ну так за этим и предлагается, иначе смысла не будет.


    S>и помешает некоторым сценариям, где иммутабл-объект изменяется в процессе конструирования.


    В конструкторе поля можно изменять многократно, это не проблема.
    А утекание абстракций контроллировать компилятором/верификатором, чтобы он запрещал изменение полей после того как из конструктора утёк this (будучи переданным некоему аргументу, допустим, для регистрации созданного объекта где-то там).

    В этой схеме невозможно создать циклические ссылки объектов друг на друга, как их невозможно создать в ФП, но никто не жалуется.
    Если уж требуются наработки иммутабельности из ФП — то берите их готовые.


    V>>Но ты просил гарантии, поэтому я и продемонстрировал ср-ва для выражения гарантий.

    S> Повторюсь: гарантии в продемонстрированном вами подходе никак не зависят от применения ключевого слова const.

    -1
    Рассуждая таким образом можно договориться до того, что и язык Ассемблера даёт такие же гарантии.
    Конечно, С++ даёт несравнимо больше гарантий в плане проектирования иммутабельных типов.

    Тебе надо просто взять однажды компилятор и чуть поупражняться в проектировании иммутабельных иерархий в плюсах, чтобы ты понял о чём речь.
    Сколько раз компилятор подсказывает о константности, т.е. не пропускает компиляции при нарушении контракта — это тебе не передать.
    В случае же подхода Immutable из дотнета — это только тестами корректность проверять и то, плохо понятно как именно, бо приватные кишки классов не проверишь.
    Остается верить на слово, бгг...

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

    Говорить "это одно и то же" — сверкать ярчайшим нубством, сорри.
    Хоть бы посмотрел на readony ref, scope и readonly методы структур в дотнете.
    Похоже, ты со всем этим еще не разбирался, но мнение имеешь.


    S>Если вы его уберёте, ImmutableValue<> останется ровно на столько же иммутабельным, как и был.


    Это из-за необычно сильно ограниченного твоего мышления, через которое я уже устал продираться.
    Как тебе недавно введенные управляемые ссылки как поля ref-структур?
    Я об этом говорил еще примерно в 2005-м, но некоторые столь же узко мыслящие даже не понимали — а зачем?

    Ты хоть понимаешь — зачем?
    И почему это стало возможно только с введением readonly ref?

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


    V>>Пусть по-мелочам больше думает машина, чем программист.

    S>Непонятно, что вы имеете в виду. Я всё ещё не вижу сценария, который вы предотвращаете при помощи этого ключевого слова.

    Да пофик.
    Первый раз сказал "не вижу" — объяснили.
    Если и после этого "не вижу" — списали на берег.


    V>>Хорошо, что согласился хоть с чем-то. ))

    V>>Проблематика заданного именно тобой обсуждения как раз в интерфейсах — ты хотел писать обобщённые алгоритмы в условиях гарантий.
    S>Разве? Может, я что-то подзабыл. Можно указать, где именно я настаивал на интерфейсах?

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


    V>>Оно и так в реальной жизни отродясь иммутабельно для State и до введения готовых Immutable-реализаций.

    S>"Оно" — это кто?

    Это объект-состояние ДКА для автомата.


    V>>В общем, будет отличаться тем, бо абстракция ImmutableMapWrapper бесплатна, а обёрнут может быть не только конкретный std::map.

    V>>Ну и, из-за наличия частичного определения шаблонов, может быть обёрнут даже тип, который не совместим по публичному интерфейсу в std::map.
    S>Как только вы обернёте не только конкретный sdd::map, появится и возможность "хулиганить".

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


    V>>А как тебе arg... — семантика?

    S>У меня со времён params в С# такой синтаксис удивления не вызывает.

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


    S>А семантика, на первый взгляд, слабже — не так удобно извлекать коллекцию


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

    При надобности оно рассматривается как initializer_list.

    Я вообще удивляюсь, как ты дожил до своих лет и тебя до сих пор не прибили за аксиматическую позицию "все вокруг дураки", когда реальность подтверждает обратное? ))


    S>и принудительная замена итерации рекурсией не выглядит шибко привлекательно.


    ЧТД, вот тебе пример того, что дураков вовсе не вокруг искать надо.

    При вычислении вложенных вызовов ф-ий для arg... получается линейный код вычисления аргументов ф-ий от самых глубоких к внешним.
    Это абсолютно линейный код верхнего уровня, последовательность call.

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


    S>Вот если бы ещё шарп починили, разрешив приписывать params к параметру любого типа, который реализован массивом. В частности, IEnumerable<T> и IList<T>.


    То сделали бы эти случаи неразличимыми/конфликтующими, в то время как м/у ними сильное различие с т.з. эффективности.
    В кишках дотнета голые массивы весьма входу и популярны.
    Где приходится бороться за эффективность на юзверском уровне — тоже.
    Отредактировано 29.03.2023 20:47 vdimas . Предыдущая версия . Еще …
    Отредактировано 29.03.2023 20:38 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 20:18 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 20:16 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 20:13 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 20:05 vdimas . Предыдущая версия .
    Отредактировано 29.03.2023 20:03 vdimas . Предыдущая версия .
    Re[16]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 30.03.23 07:02
    Оценка:
    Здравствуйте, Sinclair, Вы писали:

    S>Иммутабельность означает не просто отсутствие модифицирующих операций, но и наличие операций по построению новых значений на основе существующих.


    Думаю, надо расставить все точки на i, а то ты окончательно запутал потенциальных читаталей.

    Иммутабельность — это именно то, что означает этот термин.
    Это некие гарантии ЯП неизменности неких значений.

    наличие операций по построению новых значений на основе существующих

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

    Наверно ты имел некие св-ва, которая даёт иммутабельность программе?
    Например:
    1. свойство ссылочной прозрачности, т.к. состояние лямбд неизменно, то любые лямбды становятся чистыми ф-иями;
    2. детерминированность по чтению при доступе из разных потоков.

    Каждое из этих св-в имеет вполне ощутимые последствия.
    (1) даёт возможность не разыменовывать ссылки с целью копирования результата (ссылки на ф-ии в том числе), то бишь можно отложить вычисления "на потом", а будучи однажды вычисленным — заменить ссылку и/или выражение на его значение. При этом, доступ к любым данным по "фиксированному адресу" (например, по коль-угодно глубокому графу) можно заменить ленивой функцией с мемоизацией, которая вычислится лишь однажды.

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

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

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

    Но в этих типах не будет ни ссылочной прозрачности (о которой "знал" бы компилятор или некий пользовательский код, строящий и исполняющий лямбды "на лету"), ни автоматического распараллеливания вычислений с потенциальной мемоизацией (подменой ссылок на термы в процессе аппликации вычисленными значениями) — ничего этого нет. Потому что эти типы неотличимы от мутабельных с т.з. системы типов языка. Да и в самом языке нет пребразований над иммутабельными типами данных и ф-иями/ламбдами поверх них за отсутствием оных.

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

    Что же касается "гарантий", который требовал Синклер — он имел ввиду защиту от дурака на уровне ср-в языка.
    Например, можно описать обычные типы данных без гарантий иммутабельности, т.е. ни защитив сами данные ни const/readonly, ни дизайном ООП-типов через инкапсуляцию.
    Иммутабельность данных может быть обеспечена самим сценарием. Это примерно то же самое, что инкапсуляция на уровне объекта, только уровнем повыше — на уровне некоей подсистемы, если спроектировать её так, чтобы "абстракции не протекали", т.е., чтобы не нарушалась принятая в системе стратегия рассматривать некие данные как иммутабельные, т.е. будучи однажды созданными, сохраняющие затем неизменность до тех пор, пока на эти данные есть ссылки, т.е. пока они нужны.
    Отредактировано 30.03.2023 9:16 vdimas . Предыдущая версия . Еще …
    Отредактировано 30.03.2023 7:20 vdimas . Предыдущая версия .
    Отредактировано 30.03.2023 7:19 vdimas . Предыдущая версия .
    Отредактировано 30.03.2023 7:10 vdimas . Предыдущая версия .
    Re[17]: понимание ООП Алана Кея
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 30.03.23 07:25
    Оценка: :)
    Здравствуйте, vdimas, Вы писали:
    V>Если речь про структурное построение, а не про арифметические или логические вычисления,
    Вы зря противопоставляете эти две концепции.
    var a = 5;                            var a1 = a + 1;
    var d = DateTime.Now();               var d2 = d.AddDays(1);
    var l = (new {42}).ToImmutableList(); var l2 = l.Add(1);

    Во всех трёх строчках происходит одно и то же — конструируется новое значение на основе старого, старое остаётся неизменным.
    То, что первая и вторая строчка = "арифметические операции", а третья — "структурное построение", ещё менее важно, чем различия в синтаксисе между оператором + и вызовом метода.

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

    Ну конечно же нет. Вовсе не любой язык даст вам возможность сконструировать иммутабельную структуру, кортеж или их аналог по образцу, но с некоторым отличием.
    Ну, то есть формально говоря так можно сделать в любом языке, но если в синтаксисе языка нет таких средств, вы просто не захотите писать в таком стиле.
    Запись в стиле p.Text = "Hello", которая изменяет значение поля в структуре p, есть примерно везде.
    А вот радости писать p1 = new Foo(p.Field1, p.Field2, p.Field3, new Bar(p.Field4.Field1, p.Field4.Field2), p.Field5 + 1, "Hello", p.Field7) — крайне мало.
    На С++, наверное, можно что-то намутить через лямбды, которые будут скармливаться "конструктору модификации" — но это опять-таки с необходимостью требует наличия двух различных классов, работающих в тандеме — мутабельная основа и иммутабельная обёртка.

    Точно так же неудобно расписывать каждый раз
    var tmp = new List<int>(l); tmp.Add(1); var p2 = tmp.ToImmutableList();


    Далее — даже если у вас есть синтаксически приемлемый способ конструировать новое значение на основе предыдущего (например, для списка это так в С++), нужна ещё и приемлемая производительность. В этом месте перспектива "да я сейчас напишу однократно универсальный шабонный wrapper для любого mutable типа" начинает растворяться.

    V>Например:

    V>1. свойство ссылочной прозрачности, т.к. состояние лямбд неизменно, то любые лямбды становятся чистыми ф-иями;
    Лямбды и иммутабельность связаны примерно никак. И состояние лямбд может быть мутабельным, и иммутабельность может быть полезна и без лямбд.
    Опять — у вас в голове каша, где ортогональные понятия спутаны во что-то одно.
    V>2. детерминированность по чтению при доступе из разных потоков.
    Слова "по чтению" тут лишние. Детерминированность при доступе из разных потоков при иммутабельности работает всегда.
    В частности, ничего не должно мешать одному потоку выполнять list.Add(5), а другому одновременно выполнять list.Remove(0). Детерминированность вполне сохраняется и здесь.

    V>(1) даёт возможность не разыменовывать ссылки с целью копирования результата (ссылки а ф-ии в том числе), то бишь отложить вычисления "на потом", а будут однажды вычисленным — заменить ссылку и/или выражение на его значение.

    Это утверждение не имеет физического смысла вне контекста лямбд.
    V>(2) дает возможность эффективного распараллеливания программ, т.е. без блокировок. Распараллеливания не только по данных, но и вообще по вычислениям.

    V>Большинство типов из неймспейса Immutable не представляют практического интереса для программиста, такие как ImmutableArray, ImmutableList, ImmutableQueue и т.д., т.к. в реальных сценариях экономят совсем мало на реализации своих аналогов. Иммутабельные хеш-таблицы из этого неймспейса можно смело сжечь. Более-менее интерес представляют лишь иммутабельный словарь и множество, оба на основе сбалансированного дерева. Единственные, мать его, полезные два типа из всего неймспейса.

    .
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[12]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 30.03.23 08:45
    Оценка:
    Здравствуйте, Ночной Смотрящий, Вы писали:

    НС>>>Там не совсем тот P-код. Там просто упакованноое в удобное для интерпретации представление AST было

    V>>Там коды "инструкций" Бейсика.
    НС>Там коды ключевых слов.

    В ассемблере тоже "коды ключевых слов".
    Или ключевые слова для кодов, на вкус.


    НС>Причем организовано это строго как написано.


    Скорее, наоборот — написать можно строго как положено согласно семантике инструкции.
    Ты не сможешь написать строку программы, содержащую ошибку — тебе не дадут выполнить Enter в таких системах, пока ошибка не будет исправлена.


    НС>А если посмотреть код "компилятора", то там простейший парсинг и просто подмена символов на их коды.


    Верно, как в ассемблере.


    НС>Причем оригинал не сохраняется, а восстанавливается при необходимости по этому коду.


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


    НС>Все. На Р-код это похоже исключительно в силу того, что на Р-код похож сам исходник того бейсика.


    Да пофик. Если существует возможность "железной" реализации инструкций бейсика в гипотетическом CPU, то можно смело обзывать p-кодом.
    Целиком сокращение означает "псевдо-код", если еще развернуть — "код для псевдо-ЦПУ".


    НС>Но смысл этого — сэкономить память на хранении программы и убрать из интерпретатора парсинг, а не обеспечить переносимость.


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

    Смысл псевдокода зачастую в компактности и готовности к исполнению прямо в своём виде.
    Закодированный код Бейсика полностью удовлетворяет этим требованиям.

    Например, MS P-Code был разработан и использовался в VB/VBA из соображений компактности и эффективности, а не соображений портируемости.


    НС>>>а в описываемых Паскалях это был машинный код виртуального CPU, как сейчас в Java и дотнете.

    V>>Немного не понимаю, почему прицепились именно к Паскалю? ))
    НС>Я же процитировал:
    НС>Two early compilers generating p-code were the Pascal-P compiler in 1973, by Kesav V. Nori, Urs Ammann, Kathleen Jensen, Hans-Heinrich Nageli, and Christian Jacobi, and the Pascal-S compiler in 1975, by Niklaus Wirth.

    Я на это уже отвечал — впервые оно было не в Паскале и позже широко использовалось тоже не в Паскале.
    Понятие псевдокода существовало задолго до 73-го года.


    V>>Технология впервые была использована в другом языке.

    НС>Было что то похожее. А в полноценном виде это были именно Паскали.

    1. В полноценном виде существовали Паскали и до 73 безо-всякого p-кода.
    2. Полноценно эта технология использовалась и до реализации на ней Паскалей.

    Из одной из разработок Паскаля родился мем p-code как сокращение от псевдо-код, и ничем более Паскаль в этой истории не примечателен. ))

    Сама та разработка идиотская, ИМХО, т.к. Вирт породил Паскаль упростив Алгол-60, т.е. выкинув оттуда прилично конструкций.

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

    Нахрена учебному языку создавать переносимый бинарный код?


    V>>Мой поинт был в отделении понятия "язык программирования", как совокупности ситнаксиса и семантики от способа реализации этой семантики на стадии исполнения кода

    НС>И при этом ты привел в пример спектрумовский бейсик в котором никакого отделения синтаксиса не было, был просто вынос стадии парсинга на этап сохранения строки в память.

    Ну, дык, отличный пример отделения языка от реализации.
    Для сравнения, реализация VB For DOS честно компиллировала текстовый исходник в честный бинарь.
    При этом язык практически тот же (проги из спектрумовского Бейсика компилировались без изменений, если не лезли в память напрямую).


    V>>Спекулировать тут можно только насчёт того, что в том же дотнете рефлексия, ExpressionTree и Roslyn идут в базовой поставке и являются частью стандарта, а в С++ это дополнительный инструментарий,

    НС>Вот именно. Нет ABI — все, куча сценариев закрывается.

    ABI есть, но для конкретного компилятора/платформы.
    Например, COM ввел часть устоявшегося на x86 С++ ABI в кач-ве своего стандарта (в разделе виртуальных ф-ий, их организации и вызова).


    НС>Но вы тут опять с терминами устроили кашу, уж не знаю, случайно или намеренно. Р-код это непосредственно императивные инструкции и это отдельная песня.


    Это ты озвучиваешь своё эвристическое понимание.
    P-код — это то же, что и псевдокод. Всё.


    НС>А rtti — это другая песня, хоть и упаковываетcя она в те же файлы. Зачем пытаться это все обсуждать одновременно — непонятно.


    Была поставлена задача передать код на другую платформу и исполнить.
    Потом выяснилось, что задача звучит не так, ну да ладно...
    Отредактировано 30.03.2023 9:31 vdimas . Предыдущая версия .
    Re[18]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 30.03.23 09:13
    Оценка:
    Здравствуйте, korvin_, Вы писали:

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

    _>При чём тут мемы?

    fist class citizen — это мем.


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

    _>Какая неизвестная структура у указателя?

    Хотя бы расположение бит и их итерпретация.
    Например, far указатель унутре составной, часть бить отвечает за сегмент, часть за смещение сегмента.

    Простая конкатенация бит адреса не создаёт реальный адрес, поэтому сравнивать произвольные far адреса нельзя.
    Можно сранивать адреса только из одного сегмента.
    Или более строго — из одного выделенного по malloc блока.


    V>>"Ортогональность" появилась позже, по мере развития ЯВУ, но тогда и определение надо было доработать.

    _>Определение и доработали, я приводил цитату из 4-х пунктов.

    Ты привёл из вики, т.е. совершил напрасный труд. ))

    И почему ты споришь со мной?
    Там коллега сказал, что Expression<T> — это первоклассная сущность.
    Ну вот и спроси у него, чем его Expression<T> первокласснее моих неких MyExpression<T>?


    V>>Строго говоря, первоклассной сущностью в C# являются ссылки на объекты, но не сами объекты, внезапно.

    _>Строго говоря, это словоблудие. Косвенность сегодня никого особо не волнует.

    Косвенность не волнует только в иммутабельных системах типов, а иначе волнует, конечно.


    _>P. S. Дополнение к примеру syntax-case:

    _>
    _>(library (syntax-utils)
    _>


    Это какая-то нестандартная Схема? ))
    Вроде бы так:
    (library "<name>" "<scheme>"



    _>— всё, никаких шаблонов. Да и syntax-case можно убрать вообще, убрав даже (_ . body)


    Любые примитивы из стандарта, навроде let, могут быть реализованы как встроенные.
    Показанная тобой реализация предназначена для динамического исполнения машинкой, ес-но.
    Поэтому, опять мимо.
    Re[13]: понимание ООП Алана Кея
    От: Ночной Смотрящий Россия  
    Дата: 30.03.23 09:28
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    НС>>>>Там не совсем тот P-код. Там просто упакованноое в удобное для интерпретации представление AST было

    V>>>Там коды "инструкций" Бейсика.
    НС>>Там коды ключевых слов.
    V>В ассемблере тоже "коды ключевых слов".

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

    НС>>Все. На Р-код это похоже исключительно в силу того, что на Р-код похож сам исходник того бейсика.

    V>Да пофик.

    Нет, не пофик. Мы обсуждали идею прежде всего, а не особенности реализации. А идеи там не было.

    V>>>Мой поинт был в отделении понятия "язык программирования", как совокупности ситнаксиса и семантики от способа реализации этой семантики на стадии исполнения кода

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

    Непонятно. Что значит отделения и зачем он в этом топике?


    НС>>Но вы тут опять с терминами устроили кашу, уж не знаю, случайно или намеренно. Р-код это непосредственно императивные инструкции и это отдельная песня.

    V>Это ты озвучиваешь своё эвристическое понимание.
    V>P-код — это то же, что и псевдокод. Всё.

    Bytecode (also called portable code or p-code) is a form of instruction set designed for efficient execution by a software interpreter.
    ...
    Since bytecode instructions are processed by software, they may be arbitrarily complex, but are nonetheless often akin to traditional hardware instructions: virtual stack machines are the most common, but virtual register machines have been built also.

    https://en.wikipedia.org/wiki/Bytecode

    С бла бла бла не по теме все как обычно.
    ... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
    Re[18]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 30.03.23 09:30
    Оценка: :)
    Здравствуйте, Sinclair, Вы писали:

    V>>Если речь про структурное построение, а не про арифметические или логические вычисления,

    S>Вы зря противопоставляете эти две концепции.

    Э, нет, ты сказал что сказал — вполне однозначно.


    S>Во всех трёх строчках происходит одно и то же — конструируется новое значение на основе старого, старое остаётся неизменным.


    Это потому что ты косноязычный, не владеешь терминологией.
    Пытаешься "объяснить на пальцах", в то время как для объяснения инвариантности природы термов (лямбд и переменных) необходимо пользоваться терминологией, например, из лямбда исчисления Чёрча.

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


    S>То, что первая и вторая строчка = "арифметические операции", а третья — "структурное построение", ещё менее важно, чем различия в синтаксисе между оператором + и вызовом метода.


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

    В общем, есть такая ф-ия, называется identity.
    Любая переменная может быть представлена лямбдой, захватившей её адрес.
    (согласно определению, переменная — это именованный адрес)

    Фишка в том, что адрес глобальной переменной (или смещение локальной в стеке) — это константа времени компиляции, только поэтому мы можем рассматривать процесс чтения значения переменной как вызов ф-ии identity.

    Но стоит ввести иммутабельность, и тогда мы можем построить через две аналогичные ф-ии чтение и для ссылочных типов. Ву а ля!
    (ввиду того что ID(a)=a сколь угодно транзитивно)

    В этом месте любые рассуждения заканчиваются, бо далее и так всё понятно, бо оно применимо к термам-переменным и термам-лямбдам, хорош марать бумагу. ))
    За твои потуги реально сплошной испанский стыд, жалею, что ввязался в это.
    Отредактировано 30.03.2023 9:34 vdimas . Предыдущая версия . Еще …
    Отредактировано 30.03.2023 9:30 vdimas . Предыдущая версия .
    Re[14]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 30.03.23 10:13
    Оценка:
    Здравствуйте, Ночной Смотрящий, Вы писали:

    НС>В ассемблере — мнемоники. Ты, видимо, хотел написать машинный код.


    Я написал как это видит "процессор". ))
    Ты же тоже на системотехника учился, т.е. у тебя был курсовик разработки процессора с ограниченным набором инструкций (инструкции выдавались по вариантам).
    По вариантам так же была реализация на микропрограмме из ПЗУ или железным автоматом.
    Вот прямо в любой из этих реализаций код инструкции — это то, по чему именно твоя схема проводила диспетчеризацию.

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


    НС>Нет — там не просто коды. Там еще и определенный бинарный формат, абсолютные и относительные числовые смешения вместо меток


    Блин, ну как и виды адресаций в ЦПУ.
    Вот есть специальный регистр — указатель команд.
    В интерпретаторе спектрумовского Бейсика всё аналогично.


    НС>Нет, не пофик. Мы обсуждали идею прежде всего, а не особенности реализации. А идеи там не было.


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


    НС>Непонятно. Что значит отделения и зачем он в этом топике?


    Это значит, что в практике IT редко встречается так, что язык диктует способ своей реализации.
    Это было насчёт "возможно ли получение AST исходника в С++".
    Конечно, возможно.
    В первую очередь, возможно принципиально.
    Во-вторую можно рассуждать, как это сделать, используя уже имеющие ср-ва.
    Всё это я перечислял.


    НС>>>Но вы тут опять с терминами устроили кашу, уж не знаю, случайно или намеренно. Р-код это непосредственно императивные инструкции и это отдельная песня.

    V>>Это ты озвучиваешь своё эвристическое понимание.
    V>>P-код — это то же, что и псевдокод. Всё.

    НС>

    НС>Bytecode (also called portable code or p-code)

    НС>https://en.wikipedia.org/wiki/Bytecode

    Термин байткод появился еще позже.
    Да, это всё синонимы.

    Я вижу, что ты пытаешься утверждать, что байт-код таковым является только для виртуальных машин очень низкого уровня. ))
    Но этого ограничения в реальности не существует.
    Тем более, что в те года существовали даже реальные системы команд очень высокого уровня, например IBM/360.
    Там уровень многих инструкций повыше уровня инструкций мейнстримовых x64 или PowerPC.

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

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

    Но степень абстрагирования не навязывается, ес-но.
    Отредактировано 30.03.2023 11:51 vdimas . Предыдущая версия .
    Re[15]: понимание ООП Алана Кея
    От: Ночной Смотрящий Россия  
    Дата: 30.03.23 12:22
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Блин, ну как и виды адресаций в ЦПУ.

    V>Вот есть специальный регистр — указатель команд.
    V>В интерпретаторе спектрумовского Бейсика всё аналогично.

    Нет, совсем нет. Там именно исходная программа, просто в закодированном виде. Если по современному, то просто поток токенов.

    НС>>Непонятно. Что значит отделения и зачем он в этом топике?

    V>Это значит, что в практике IT редко встречается так, что язык диктует способ своей реализации.

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

    V>Конечно, возможно.

    V>В первую очередь, возможно принципиально.

    То что это, по сравнению с expression tree так и не получило достойного применения как бы намекает, что вариант с перегрузкой операторов и шаблонами имеет существенные недостатки.

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


    Не я, это общепринятое определение. И, думаю, Синклер именно его имел в виду. Если тебе хочется придумать собственную трактовку — ради бога, но тогда все твои возражения теряют релевантность. И образуется именно та логическая каша, про которую я написал.
    ... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
    Re[19]: понимание ООП Алана Кея
    От: korvin_  
    Дата: 30.03.23 16:54
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>fist class citizen — это мем.

    :wut:

    V>Хотя бы расположение бит и их итерпретация.


    Так и что в этом неизвестного?

    V>Например, far указатель унутре составной, часть бить отвечает за сегмент, часть за смещение сегмента.

    V>Простая конкатенация бит адреса не создаёт реальный адрес, поэтому сравнивать произвольные far адреса нельзя.
    V>Можно сранивать адреса только из одного сегмента.
    V>Или более строго — из одного выделенного по malloc блока.

    Вот видишь, тебе это известно, тогда что неизветсно?

    V>Ты привёл из вики, т.е. совершил напрасный труд. ))


    V>И почему ты споришь со мной?

    V>Там коллега сказал, что Expression<T> — это первоклассная сущность.
    V>Ну вот и спроси у него, чем его Expression<T> первокласснее моих неких MyExpression<T>?

    Ничем, это булево свойство.

    V>Косвенность не волнует только в иммутабельных системах типов, а иначе волнует, конечно.


    Не для определения «first-class citizen»

    V>Это какая-то нестандартная Схема? ))


    Стандартная R6RS.

    V>Любые примитивы из стандарта, навроде let, могут быть реализованы как встроенные.


    И что?

    V>Показанная тобой реализация предназначена для динамического исполнения машинкой, ес-но.


    что? Какое ещё динамическое исполнение, если фаза expand происходит до компиляци?

    V>Поэтому, опять мимо.


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

    Или хотя бы стандарт почитай:

    Macro uses (see section 9.2) are expanded into core formsat the start of evaluation (before compilation or interpretation) by a syntax expander.

    http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-13.html#node_chap_10
    Отредактировано 30.03.2023 17:02 korvin_ . Предыдущая версия . Еще …
    Отредактировано 30.03.2023 17:00 korvin_ . Предыдущая версия .
    Отредактировано 30.03.2023 16:57 korvin_ . Предыдущая версия .
    Re[16]: понимание ООП Алана Кея
    От: vdimas Россия  
    Дата: 30.03.23 22:31
    Оценка: :)
    Здравствуйте, Ночной Смотрящий, Вы писали:

    V>>В интерпретаторе спектрумовского Бейсика всё аналогично.

    НС>Нет, совсем нет. Там именно исходная программа, просто в закодированном виде. Если по современному, то просто поток токенов.

    Как я и думал, собсно.
    Для некоторых команд проца IBM/360 аргументы тоже представляют из себя эдакие токены.


    НС>>>Непонятно. Что значит отделения и зачем он в этом топике?

    V>>Это значит, что в практике IT редко встречается так, что язык диктует способ своей реализации.
    НС>Но тот бейсик как раз демонстрирует обратное.

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


    V>>Конечно, возможно.

    V>>В первую очередь, возможно принципиально.
    НС>То что это, по сравнению с expression tree так и не получило достойного применения как бы намекает, что вариант с перегрузкой операторов и шаблонами имеет существенные недостатки.

    Вообще-то получило.
    Интроспекция дерева выражений Boost.Proto может происходить не только в рантайм, но и в момент компиляции через диспетчеризацию на шаблонах, т.к. любое выражение полностью статически типизировано и представляет из себя "плоский" граф, построенный на value-type в терминах дотнета.

    Например, выражение expr:
    auto x= _1;
    auto y = _2;
    auto expr = sin(x*10 + y*20);

    порождает (на пальцах если) примерно такую структуру:
    sin_(plus_(mul_(arg_<0>, const_<10>()), mul_(arg_<1>, const_<20>)))


    На шарпе это примерно так:
    struct SinFn<TExpr, TCtx> 
        where TExpr : struct, IExpr, IConvertibleToDouble 
              TCtx : struct, ICalcContext
    {
        private TExpr _arg;
    
        public SinFn(TExpr arg) {
            _arg = arg;
        }
    
        public DoubleValueExpr Invoke(in TCtx ctx) {
            return Math.Sin(_arg.Invoke(ctx).ToDouble());
        }
    }
    
    struct IntBinPlusOp<TExpr1, TExpr2, TCtx> 
        where TExpr1 : struct, IExpr, IConvertibleToInt 
              TExpr2 : struct, IExpr, IConvertibleToInt 
              TCtx : struct, ICalcContext
    {
        TExpr1 _arg1;
        TExpr2 _arg2;
        ...    
        public IntValueExpr Invoke(in TCtx ctx) {
            return _arg1.Invoke(ctx).ToInt() + _arg2.Invoke(ctx).ToInt();
        }
    }

    и т.д.
    (я когда-то сделал такой калькулятор на value-типах в середине нулевых, как вышли генерики, где-то валяется)

    Через конкретную реализацию CalcContext передаются связанные переменные (внешние захваченные или аргументы из сигнатуры), к которым (переменным) в нужных местах происходит обращение.

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

    Эффективность её интерпретирования в плюсах на порядок выше эффективности интерпретирования Expression<T> (до компиляции).

    Соответственно, основное применение Boost.Proto — это compile-time генерация кода из некоего DSL (Boost.Proto, в отличие от Expression<T> позволяет переопределять дефолтную грамматику, доступные операторы, терминалы/нетерминалы, "ключевые слова" и т.д.).
    В т.ч. можно в compile-time производить трансформацию AST, т.е. "компиллировать" свой DSL.

    И напротив, связка linq/expression/queryable с подключаемыми провайдерами — чисто динамическая вещь.

    Можно ли аналогичное сделать в С++, т.е., породить такое AST, которое возможно будет итерировать в рантайм не шаблонным кодом, а через систему интерфейсов (с целью динамической трансформации AST подключаемыми в рантайм провайдерами)? — можно. Библиотека Boost.Proto и разработана с целью расширять дефолтные типы узлов AST, обогащая их требуемыми признаками.

    Почему не было сделано?
    Предположу, что по той причине, что работа с БД давно уехала с клиента на сервер и даже на веб-сервер, где плюсы не в топе популярности.
    Когда плюсы теряли популярность в вебе, средний пинг по планете был 50-100 ms, т.е. экономия единиц ms и даже единиц десятков ms ничего не давала.
    К тому же, основные задержки по-прежнему в БД.


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

    НС>Не я, это общепринятое определение. И, думаю, Синклер именно его имел в виду.

    Как выяснилось, Синклер имел ввиду и вовсе AST-выражения, которые хотел передать удалёному ендпоинту, т.е. оно ближе к коду Бейсика, а не к низкоуровневому байт-коду. ))


    НС>Если тебе хочется придумать собственную трактовку — ради бога


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


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


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

    Был у нас коллективный курсовик для всей группы на 3-м курсе, в рамках которого была разработана учебная ОС.
    В состав ОС входило много чего плюс компилятор учебного советского языка (нц/кц, если помнишь) в байт-код стековой машинки.

    Угадай с одного раза, зачем мы компиляли в байт-код?
    Очевидно же — потому что мы были не способны за трудоёмкость курсовика (плюс небольшой свой опыт) компилять это всё в машинный код x86 со всеми его бесконечными спецификациями.
    Это нетривиальная задача — порождать "ассемблерный код" реального нехилого проца.

    Зато на языке высокого уровня (Си) написать интерпретатор своего упрощённого байт-кода — как два пальца об асфальт выделенной группе из 3-х студентов.
    В итоге, ни на одном из этапов разработки мы не выходили на низкий уровень, кроме собственного уютного примитивного байт-кода.

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

    Но это мы, студенты-третьекурсники.
    А когда мне показывают, что похожий по технике Паскаль был разработан взрослыми инженерами... Реакция была как на кислый лимон.
    Те тоже не стали пачкать ручки оптимизацией и низким уровнем, бгг.
    Вы, ребят, даже не сообразили, на что даёте ссылки. ))
    Отредактировано 01.04.2023 13:04 vdimas . Предыдущая версия . Еще …
    Отредактировано 30.03.2023 23:04 vdimas . Предыдущая версия .
    Отредактировано 30.03.2023 23:03 vdimas . Предыдущая версия .
    Отредактировано 30.03.2023 22:58 vdimas . Предыдущая версия .
    Отредактировано 30.03.2023 22:33 vdimas . Предыдущая версия .
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.