Comments 410
school = School.new
school |> add_student("Aimee", 2)
Такой код вернет undefined function add_student/3
, разве нет? Эликсир ведь не знает, что add_student
находится в модуле School
?
Вот, например, функция students_by_grade. Первый параметр — школа, второй — оценка. Но функция передается в пайп с оценкой, т. е. вторым параметром.
Специальный хак для первого параметра? Частичное применение задом-наперед?
c(b(a)))
# эквивалентно
a |> b |> c
c(b(a1, a2), b2, b3))
# эквивалентно
a1 |> b(a2) |> c(b2, b3)
f |> g = g f
И, соответственно, нет никаких специальных правил про первый аргумент и прочее. Впрочем, это не мешает эргономике — просто self-аргумент последний, а не первый.
Не подкинете ссылок на какие-нибудь заметки о дизайне Elixir? Интересно, почему авторы решили отойти от проторенной дорожки.
Ну и как по мне, это логичнее, когда, допустим, функции для работы со списками принимают список в качестве первого аргумента, а не последнего.
Заметок о дизайне на эту тему я не встречал, но можно почитать в дискуссии в google-группе, например эту.
По сути любой объект в ООП это какое-то значение определенного типа, над которым производятся какие-либо действия.
Т.о запись любой метод применимый к этому объекту это всего лишь некая функция, которая берет этот объект, берет доп. параметры и что-то делает, на выходе функции получается новый объект.
Нотации для записи вообще не имеют значения:
что ты напишешь: a->add_something(value)
что add_something(a, value)
Всё это дело вкуса.
Другой вопрос. Многие говорят о вреде глобального состояния (всякие глобальные переменные и т.п.).
Но почему-то никто не поднимает вопрос, о вреде глобального состояния в контексте объекта.
Когда с помощью прямых присваиваний типа:
this->my_property = 1 мы по сути изменяем глобальное свойство в объекте.
Т.о. когда внутри метода встречаются такого рода вызовы, то метод априоры не может быть чистым. А это уже как раз к вопросу «о глобальных состояниях».
Чистый метод (функция) всегда берет объект и возвращает новый.
Но почему-то никто не поднимает вопрос, о вреде глобального состояния в контексте объекта.
Может быть, потому что это не глобальное состояние, а локальное?
Проблема в том, что вы таким образом лишаетесь возможности различать "истинно глобальное" состояние — то, которое глобально для всей системы и "слегка глобальное" — которое на самом деле локально для объекта. Учитывая, что локального для метода состояния, считай, и нет (бессмысленно считать незамкнутые переменные состоянием), то это вообще теряем смысл.
С другой же стороны, если лишить объекты mutable-состояния, то вам придется нарушать инкапсуляцию: каждый метод должен будет оповещать всех его потребителей о том, что он изменил состояние, теперь они должны иметь дело с новым объектом (кстати, а что делать тем, у кого осталась старая ссылка?). И, что веселее, это распространяется по каскаду вверх: у пользователей объекта тоже меняется состояние, об этом тоже надо оповещать, и так до корня. Очень весело. И это мы еще не касались вопросов производительности.
В этом отношении подход акторов (как он показан здесь, и как сделано с, если мне память не изменяет, бихевиорами в акке) выглядит весьма сбалансированным: у пользователей есть ссылка на актора, которая не зависит от его внутреннего состояния, но инфраструктура внутри ведет себя функционально — каждый метод получает предыдущее состояние и возвращает новое. Тут, правда, надо помнить, что акторный подход заодно гарантирует, что в один момент времени выполняется только один метод, и он будет выполнен до конца (т.е. нет проблем с конкурентностью изменения состояния).
При всем при этом я отдаю должно уважение иммутабельным объектам. Просто они не везде удобны.
Акторы нужны там, где есть действительно объект который живет своей жизнью.
Пример:
Вот есть у вас некая ORM. Эта ORM возвращает записи о школьниках. В контексте статьи описывается принцип когда каждый школьник — актор. Но в данном случае, школьник лишь некая запись, с которой можно работать как с объектом, любая функция которая изменяет внутренее состояние школьника, возвращает новый «объект» школьника. Если у вас в системе живет сессия этого школьника, то да, состояние этого актора может включать «объект» школьника для учета внутреннего состояния сессии, но это не значит что каждый «объект» следует делать актором — это вредно.
В контексте статьи описывается принцип когда каждый школьник — актор.
Это где, простите? В статье описана ситуация, когда актор — это реестр школьников.
Но в данном случае, школьник лишь некая запись, с которой можно работать как с объектом, любая функция которая изменяет внутренее состояние школьника, возвращает новый «объект» школьника.
Зачем? (не говоря уже о том, что это не "как с объектом" в моем понимании)
школьник лишь некая запись, с которой можно работать как с объектом, любая функция которая изменяет внутренее состояние школьника, возвращает новый «объект» школьника.В Elixir для этого есть структуры, которыми и оперируют «ORM» типа Ecto. В Erlang для этого же есть записи.
И как правильно заметил lair в статье нет никакого призыва использовать акторы вместо структур. Зато есть призыв с точностью до наоборот, не тащить ООП на уровень, где оно не нужно. В вашем примере функции будут работать со структурой Школьник, как со структурой, а не как с объектом. Грубо говоря, примут на вход одну структуру, а на выход вернут совсем другую.
Чтобы сначала была имплементация модуля для такой структуры с основными операциями, а уже потом gen_server должен содержать минимальный код для приема собщений и вызова соотвествующих операций с данной структурой.
Ну и вызывающий код незначительно поменяется:
school |> add_student(%Student{name: "Aimee"}, 2)
По поводу состояния, в Elixir оно надёжно спрятано в процессе, никакой рефлексией и уж тем более прямыми присваиваниями до него не достать. Только отправкой сообщений можно опосредованно влиять на состояние. Ну и как в любом функциональном языке все данные неизменяемы. Т.о. ситуация, когда вы получили часть состояния объекта по ссылке, передали в другой метод, а он взял его и изменил, тоже невозможны by design.
Что будет, если я сделаю вот так?
pid = Airplane.new
School.add_student(pid, "Aimee", 2)
(FunctionClauseError) no function clause matching in Airplane.handle_cast/2
airplane.ex:38: Airplane.handle_cast({:add, "Aimee", 2}, %{})
Вообще Erlang и Elixir стимулирует разработку по принципу «Let it crash». Поэтому чуть что не так — процесс падает и перезапускается с чистого листа одним из супервизоров.
То есть я правильно понимаю, что (а) это ошибка периода выполнения (а не компиляции/статического анализа) и (б) падает не вызывающий процесс, а вызываемый?
Теоретически такое можно отследить каким-нибудь статическим анализом, но я не встречал реализации пока что. И такие ошибки надо отлавливать в тестах.
А падает в данном случае зацикленный GenServer. Если add_student внутри использует cast — вызывающий процесс ничего не заметит. Если call — зависит от реализации, но если вы не отлавливаете ошибки — упадёт вызывающий процесс тоже.
В любом случае, скорее всего у вас должен быть супервизор который перезапустит или один или оба генсервера.
Если Вы хотите, чтобы процесс не падал в таких ситуациях, можно определить catch-all обработчики сообщений, которые просто проигнорируют «левые» сообщения. Но с учётом того, что подобное может произойти только из-за ошибки/опечатки в коде, лучше пусть падает.
Если вы решите делать такое в реальном коде — функция new
не приветствуется.
{:ok, school} = School.start_link
school |> School.add_student("Aimee", 2) # => :ok
school |> School.students_by_grade(2) # => ["Aimee"]
school |> School.students_by_grade # => [[grade: 2, students: ["Aimee"]]]
Таким образом каждый процесс ведёт себя с одной стороны подобно серверу — отсюда и название GenServer, а с другой стороны подобно объекту — согласно описанию Кэя.
<…>
Таким образом, Elixir позволяет вам применять ООП там, где оно действительно работает, — на верхнем уровне проектирования системы. И при этом не усложнять нижние уровни системы надуманными абстракциями и контрпродуктивными тезисами типа «Всё есть объект».
Посыл понятен (в части второй цитаты), но тогда не надо сюда привлекать Кэя, который первым пунктом почему-то (видимо, по наивности своей думал о простоте) поставил именно «надуманный и контр-продуктивный» принцип «все есть объект». А то как-то прям не честно получается :)
Как Вам помогает в проектировании системы то, что 1 — это не 1, а экземпляр класса Integer с внутренним состоянием равным 1, к которому никто по идее не должен иметь доступ напрямую?
Как Вам помогает в проектировании системы то, что 1 — это не 1, а экземпляр класса Integer с внутренним состоянием равным 1, к которому никто по идее не должен иметь доступ напрямую?Помогает тем, что с Integer-объектом я общаюсь ровно так же, как с любым другим объектом в системе — через сообщения. А что там у него внутрях мне как раз безразлично. Система, где есть несколько разнотипных сущностей, при прочих равных будет сложнее системы, где все сущности одинаковы. Вопрос заключается в «прочих равных» — не факт, что там не будет каких-то («контр-продуктивных») потерь…
Вообще, хорошо или плохо, когда все единообразно — вопрос отдельный (не по теме статьи, как я понимаю?). Но то, что ваше определение объектности не совпадает с Кэйевским — факт: убран первый пункт. А если убрать этот пункт, то остается, по сути, только позднее связывание (так как из сообщений уходит получатель). А это было в LISP-е (о чем тот же Кэй неоднкратно говорил). Соответственно, речь таки не идет об ООП в исходном «Кэевском» смысле слова. Получается, скорее, некая имитация «антуража» ООП… Похожий на объектно-ориентированный DSL в внутри ФЯП, что ли? Это, впрочем, совсем не означает, что данный подход хуже — это тема отдельного исследования и обсуждения. Просто в IT/программировании и так все не слишком ясно, не стоит вносить дополнительную путаницу, не так ли?
Помогает тем, что с Integer-объектом я общаюсь ровно так же, как с любым другим объектом в системе — через сообщения.Так а Integer-объект то зачем? Был у нас обычный всем понятный числовой литерал, а стал «объект», у которого внутреннее состояние выставлено наружу. Почему никто не пишет 1.getValue()? Почему возникают проблемы с boxing/unboxing? Потому что нет тут никакого единообразия. Потому что число — это число, а не объект. Вот и получается, что в теории хотели упростить, а на практике только усложнили.
ваше определение объектности не совпадает с Кэйевским — факт: убран первый пункт.Насколько я понимаю, Вы ссылаетесь на Early History Of Smalltalk. Но там нигде не написано, что список идей для реализации интерпретаторов Smalltalk, является определением ООП. Не всё, что обязательно для Smalltalk, является обязательным для ООП. Иначе мы подменяем общую идею конкретной реализацией.
Но там нигде не написано, что список идей для реализации интерпретаторов Smalltalk, является определением ООП. Не всё, что обязательно для Smalltalk, является обязательным для ООП
Уиии! (И да, я с вами согласен)
В какой-то момент Стефан Рам заинтересовался этим вопросом, и пристал к Кэю с уточнением. Вот ответ:
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.
(еще там есть смешная приписка "It can be done in Smalltalk and in LISP")
Был у нас обычный всем понятный числовой литерал, а стал «объект», у которого внутреннее состояние выставлено наружу.
- Насчет состояния наружу — вы крепко ошибаетесь. Либо не на те системы смотрите. Фишка как раз в том, что никакого состояния наружу не торчит: объекту можно посылать сообщения и использовать его в качестве аргумента в сообщениях других объектов. А вот вести он себя будет в соответствии со своим значением — как и полагается объекту. Если вам это все не видно на примере целого числа, возьмите другой пример: null или true/false. Реализация их как объектов дала возможность не только сильно упростить синтаксис того же Smalltalk, но и позволяет делать красивые дизайны там, где необъектные аналоги прилично портят картину. Впрочем, это можно увидеть и на примере чисел — почитайте (а лучше попробуйте на практике), как в Smalltalk-е реализовано преобразование между «целыми короткими», «длинными», «очень длинными», дробями и т.д. Если это не удобно и элегантно, тогда что?
- Литералом же (в объектной системе, кстати, тоже объектом) 1 будет являться до момента компиляция (в зависимости от реализации, может еще таковым стать в момент представления пользователю) — это уже не зависит от языка и парадигмы (см. определение термина).
- Ну, и насчет «обычный» и «всем понятный… В объектной системе получателями и аргументами сообщения являются объекты (а что еще?). И если у вас все объекты — тут как раз все понятно и обычно. Но вот как только у вас появляются не-объекты, все сразу становится сложнее: надо придумать отдельный(-ые) механизм(-ы) для работы с такими сущностями и передачи таких сущностей.
Но там нигде не написано, что список идей для реализации интерпретаторов Smalltalk, является определением ООП.Это уже не раз обсуждалось — в том числе совсем недавно на Хабре. Разумеется, каждый может выискивать нужные ему нюансы и придираться к словам. Можно разделить понятия „объект“ и „объектно-ориентированное программирование“. Можно утверждать, что если человек, работал над некоторой идеей (название для которой он и придумал) и получил некий результат, то этот результат совсем не отражает эту самую его идею. Но если этого не делать, то слова The first three principles are what objects „are about“—how they are seen and used from „the outside“ и The last three—objects from the inside—were tinkered with in every version of Smalltalk (and in subsequent OOP designs) четко отделяют принцип от реализации: первое — что такое объекты вообще, второе — как мы их реализовали в нашей системе. Обратите также внимание на то, что когда мы пытаемся определить нечто (в данном случае термин „объект“), мы едва ли будем говорить „все является этим нечто (объектом)“. Эта фраза явно относится к чему-то большему… В общем, надо сильно постараться, чтобы, прочитав данную книгу, заявить, что это не принципы ООП.
Ну, и, наконец, есть аргумент „от здравого смысла“ — я его изложил выше. Если убрать данный пункт, окажется, что никакого ООП нет, так как все уже было до этого. Но тогда и смысла в статье нет.
Насчет состояния наружу — вы крепко ошибаетесь.Зачем Вы спорите с очевидными вещами? То, что внутреннее состояние 1 равно 1, видно без каких-либо сообщений. То же самое с null, true и false.
Вообще идея и реализация — это всегда разные вещи. Реализация может демонстрировать идею, но не ограничивать. Да и есть цепляться за эти 6 пунктов, то они не выполняются вообще нигде, даже в Smalltalk:
Apparently, «everything isn't an object» afterall — some things are primitives.© Heart Of Smalltalk
все уже было до этогоНичто не ново под Луною…
Зачем Вы спорите с очевидными вещами? То, что внутреннее состояние 1 равно 1, видно без каких-либо сообщений.Жаль, что для вас это «очевидно».
Так, вы с чем спорите-то? Что ООП подразумевает, что все является объектом? Хорошо, тогда включите в определение ООП принципы взаимодействия объектов с не-объектами. Посмотрим, что за определение получится.
Да и есть цепляться за эти 6 пунктов, то они не выполняются вообще нигде, даже в SmalltalkДа, рано или поздно мы дойдем до электронов, которые объектами не являются. И сообщения рано или поздно превратятся в вызовы или команды перехода в процессоре. Это повод отказаться от всех принципов концепции?
Обработчик сообщения, в принципе, может обращаться к другим объектам, но не обязан. А когда вы запихиваете ООП на нижние уровни, то у вас и сообщения — это объекты, состоящие из объектов, и обработчик сообщений обязан работать с другими объектами. Вот именно в этот момент «всё есть объект» уничтожает базовые идеи ООП.
На самом деле, ответ я вам уже предлагал (не здесь): граница проходит по языку. Если я работаю с объектной системой, я хочу с ней общаться на объектном языке. Если я работаю с «функционально-ориентированной» системой, я буду общаться на функциональном языке. Если же я работаю с гетерогенной системой, мне нужно знать несколько языков. Вы считаете, это хорошо?
На самом деле, ответ я вам уже предлагал (не здесь): граница проходит по языку. Если я работаю с объектной системой, я хочу с ней общаться на объектном языке. Если я работаю с «функционально-ориентированной» системой, я буду общаться на функциональном языке. Если же я работаю с гетерогенной системой, мне нужно знать несколько языков. Вы считаете, это хорошо?
Это плохо, но не потому, что вам надо знать несколько языков, а потому, что вы считаете, что с системой надо общаться на том языке, на котором она написана — что противоречит банальному сокрытию информации. А в реальности с системой надо общаться с помощью того API, которое она предоставляет — и, что любопытно, многие системы, имеющие внутри себя сплошную функциональщину, снаружи выглядят вполне объектно.
Это кстати, даже в синтаксисе языков прекрасно видно, хотя никто не замечает так как привыкли. Но привычки они не всегда полезны.
Так, в соответствии с принципом «не все есть объект», API получается разнородным. И мне надо всегда помнить и себя одергивать: здесь объект и сообщения, а здесь — уже функция и тип (или что-то еще).
Проблема в том, что вы не можете сделать все API в соответствии с принципом "все есть объект". Соответственно, эта разнородность сохранится — как внутри API, так и между разными API. А вот сделать API вида "сервис — объект, сообщения — не объекты" — легко, причем я не могу себе представить API, которое на это не ложится. А эта "разнородность" (а) минимальна и (б) семантически легко объяснима (поэтому не добавляет понятийной сложности).
Проблема в том, что вы не можете сделать все API в соответствии с принципом «все есть объект».Доказательства?
Попробуйте сделать API для физически распределенной системы, где сообщения будут объектами (с настоящим, а не вырожденным, поведением).
Да, система мало того, что физически распределенная, так еще и ее узлы написаны на разных языках. Типичный случай кровавой ынтырпрайз-ынтыграции.
Мне ли вам объяснять, что если я попробую и у меня не получится, это все равно не доказывает невозможности построения такой системы в принципе?
Если бы вы попробовали, то, возможно, поняли бы, почему это невозможно.
Но окей, пойдем с конца.
пояснение, что такое «объекты с настоящим, а не вырожденным поведением».
Объект, все поведение которого сводится к хранению-передаче (без трансформации) данных. Оно же Anemic Data Model. Иными словами, это когда все операции, поддерживаемые объектом, сводятся к парам setX
/getX
, где X
— какое-то свойство объекта.
А теперь собственно мыслительный процесс.
Предположим, у нас есть две системы, одна из них — реестр пользователей, вторая — обработчик уведомлений. Пользователь — это сущность, имеющая атрибуты email
, firstName
, lastName
. При создании пользователя в реестре мы уведомляем обработчик о создании такого пользователя (сообщением created(user)
). Одно из сконфигуренных уведомлений в обработчике — это отправка емейла пользователю, причем этот емейл должен содержать полное имя пользователя.
С точки зрения хорошего дизайна, формирование полного имени — это ответственность сущности "пользователь" (потому что мы можем формировать это имя конкатенацией, или хранить отдельно, или делать что-то третье). Но… если реестр и обработчик уведомлений стоят на разных континентах и написаны на разных языках, у нас просто нет способа передать между ними "пользователя", как объект, имеющий поведение — во-первых, технологически сложно реализовать трансляцию поведения между языками, во-вторых, в этот момент у нас станет объект "пользователь" склонируется, что приведет к расхождениям.
Окей, давайте передавать не "пользователя", а ссылку на объект "пользователь", по которой и реестр, и обработчик смогут послать любое сообщение, и получить ответ. Но вот незадача… ответ на сообщение getFullName
— это что? Объект? Тогда мы попали в замкнутый круг. Или это значение-строка? Но тогда мы нарушили униформность.
В гетерогенных распределенных системах (посмотрите на то же SOA, посмотрите на Enterprise Integration Patterns) сообщения между системами — это не объекты. У них нет поведения, у них есть только данные. И еще к ним не применимо сокрытие: любой агент на пути, будь то маршрутизатор или адаптер или что угодно еще, может полностью прочитать сообщение, изменить его произвольным образом, и переслать дальше. Или не переслать.
Когда реестр уведомляет обработчик о появлени нового пользователя, первый передает второму всю необходимую информацию (да, это не объект — см. выше, но это не проблема). А на стороне обработчика из этой информации строят полноценный объект. Так что никто пропажу объекта не замечает.
Какая именно информация необходима — само собой, «зависит». Как вы сказали, можно передать один идентификатор и сооружать прокси. Можно при желании передать полное состояние и сказать экземпляр какого класса нужно создать — если они «похожи» (тот же String, к примеру). А при отсутствии нужного класса на удаленной стороне используем либо первый вариант (как основной в нашем несовершенном современном мире), либо (если есть техническая возможность — вдруг таки на обеих сторонах умеют говорить на одном языке… или, в случае с гетерогенными системам, появится в будущем) можно спросить «что за класс?» и поделиться его реализацией. В самом общем случае сегодня второй вариант не прокатывает, но это же не означает принципиальную невозможность?
Я не имел возможности попользоваться WCF, но «по слухам» считал, что там такая схема и реализована, нет? В Smalltalk-ах примерно так работает OpenTalk (VisualWorks). Или вот — удаленная отладка в Pharo. Это я к тому, что отладчик в Smalltalk весьма такая объектная штука.
Если отбросить аргумент «электроны — не объекты», то вся проблема сводится к передаче метаинформации в нужный момент времени, нет?
Нет, конечно.
Когда реестр уведомляет обработчик о появлени нового пользователя, первый передает второму всю необходимую информацию (да, это не объект — см. выше, но это не проблема).
Это как раз проблема, потому что это и нарушает принцип "все — объект".
А на стороне обработчика из этой информации строят полноценный объект. Так что никто пропажу объекта не замечает.
Вот только в интерфейсе это не объект, а "вся необходимая информация". А что на стороне обработчика — вы и не знаете (потому что инкапсуляция).
Можно при желании передать полное состояние и сказать экземпляр какого класса нужно создать — если они «похожи» (тот же String, к примеру)
Вот только сообщение, посланное объекту на стороне получателя (обработчика в моем примере) не будет получено объектом на стороне отправителя (реестра в моем примере). У вас объект раздвоился.
Это как раз тот момент, когда то, что вы передали — не объект. Его поведение на разных сторонах не униформно.
И да, как раз это "зависит" и приводит к не-униформности API: вы что-то передаете с поведением, что-то как ссылку, что-то как значение. Намного проще (и униформнее) все передавать как значение.
А при отсутствии нужного класса на удаленной стороне используем либо первый вариант
В нашем технически несовершенном мире это (а) очень медленно и (б) просто передает проблему на следующий уровень.
Я не имел возможности попользоваться WCF, но «по слухам» считал, что там такая схема и реализована, нет?
Нет. В WCF как раз реализована схема с передачей чистых данных в том или ином формате. На любой стороне эти данные могут десериализовать в объект с поведением (если эта сторона это умеет), но это будет объект "этой стороны" — а в контракте все равно описана просто структура данных. Иными словами, для WCF-сообщения в контракте указано, какие данные в нем есть, но не описано, какие операции оно умеет.
В Smalltalk-ах примерно так работает OpenTalk (VisualWorks). Или вот — удаленная отладка в Pharo. Это я к тому, что отладчик в Smalltalk весьма такая объектная штука.
Это гомогенные среды, в них все иначе устроено.
Иными словами, для WCF-сообщения в контракте указано, какие данные в нем есть, но не описано, какие операции оно умеет.
… уточнение: это точно верно для WSDL/SOAP, я не очень помню, как это устроено в случае с пропьетарными биндингами.
Уточнение к уточнению. By Design биндинги в WCF не имеют доступа к передаваемым объектам — им данные достаются уже в сериализованном виде.
Если бы кто-то пожелал превратить WCF обратно в Remoting — ему стоило бы смотреть в сторону ContractBehavior и переопределения сериализатора.
Кстати, "такая схема" (с разделением на передаваемые и ссылаемые объекты) была реализована в Remoting, но от нее отказались как от неудобной.
Это как раз проблема, потому что это и нарушает принцип «все — объект».Он в любом случае рано или поздно нарушается (мы это давно установили, да и секрета в этом никогда не было). Весь вопрос в том, рано или поздно.
Вот только в интерфейсе это не объект, а «вся необходимая информация». А что на стороне обработчика — вы и не знаете (потому что инкапсуляция).Простите, не понял, о каком интерфейсе речь, и что мне нужно знать на стороне обработчика?
Вот только сообщение, посланное объекту на стороне получателя (обработчика в моем примере) не будет получено объектом на стороне отправителя (реестра в моем примере).Почему же оно не будет получено-то? У нас же есть слой, передающий сообщения по сети?
У вас объект раздвоился.На каждой из сторон я имею дело с объектом. Про то, что он раздвоился (детали реализации) никому из пользователей объекта знать не надо.
Это как раз тот момент, когда то, что вы передали — не объект. Его поведение на разных сторонах не униформно.При правильной реализации на обеих сторонах может быть очень даже униформно. А правильную реализацию, кстати, можно при старте приложения согласовать.
И да, как раз это «зависит» и приводит к не-униформности API: вы что-то передаете с поведением, что-то как ссылку, что-то как значение. Намного проще (и униформнее) все передавать как значение.Какая разница как я что-то передаю? Это детали реализации. Я могу вообще байт-код передавать. Могу нужную виртуальную машину на ту сторону загрузить.
В нашем технически несовершенном мире это (а) очень медленно и (б) просто передает проблему на следующий уровень.Мы же говорим про принципиальную возможность, не так ли? Пункт (а) не релевантен. А что значит «передает проблему на следующий уровень»? Рано или поздно системы должны начать разговаривать «на одном языке». И даже, вроде бы, не важно когда именно — это детали реализации, от разработчиков на обеих сторонах они должны быть скрыты.
Это гомогенные среды, в них все иначе устроено.Во! Может быть проблема-то как раз в том, чтобы реализовать объекты на том же уровне абстракции, где среды начинают понимать друг друга (становятся гомогенными)? Другими словами «протащить» объекты максимально далеко в бутстрэппинг? Что эквивалентно вопросу «минимально возможной объектной системы»? Не об этом говорит тот же Кэй, когда расхваливает Internet (в противовес Web-у)?
Другими словами «протащить» объекты максимально далеко в бутстрэппинг?Кстати, Erlang позволяет посылать сообщения процессам на удалённых нодах, их даже никуда тащить не надо и тем более раздваивать. Вот только ответ на сообщение придёт в виде данных. Что весьма удобно на практике, но не вписывается в Вашу теоретическую концепцию.
Простите, не понял, о каком интерфейсе речь, и что мне нужно знать на стороне обработчика?
Об API. И на стороне обработчика вам не надо знать, на каком языке написан реестр и наоборот.
Почему же оно не будет получено-то? У нас же есть слой, передающий сообщения по сети?
Потому что вы подняли копию объекта на стороне получателя, и теперь сообщения принимает она.
На каждой из сторон я имею дело с объектом. Про то, что он раздвоился (детали реализации) никому из пользователей объекта знать не надо.
Не надо?
repository.createUser(user)
notificationEngine.notifyCreation(user) //notificationEngine - удаленный
user.firstName = "newName" //в notificationEngine он изменился или нет?
> При правильной реализации на обеих сторонах может быть очень даже униформно.
Не в реальном мире.
> Могу нужную виртуальную машину на ту сторону загрузить.
Тем самым нарушив право той стороны на собственную реализацию.
> Рано или поздно системы должны начать разговаривать «на одном языке».
И этот язык, неизбежно, данные.
> Может быть проблема-то как раз в том, чтобы реализовать объекты на том же уровне абстракции, где среды начинают понимать друг друга (становятся гомогенными)?
Это эквивалентно "запретить всем писать на не-ООП-языках". Спасибо, но нет.
> Не об этом говорит тот же Кэй, когда расхваливает Internet (в противовес Web-у)?
Вот только в интернете между узлами гуляют данные, а не объекты. Что как бы лишний раз подтверждает.
Потому что вы подняли копию объекта на стороне получателя, и теперь сообщения принимает она.А потом транслирует на другую сторону, там сообщения принимают, обрабатывают (не зная, откуда они поступили), возвращают ответ, который транслируется назад, и там создается впечатление, что ответил мне тот объект, которому я послал исходное сообщение.
Тем самым нарушив право той стороны на собственную реализацию.Что это за право? :) У компьютерных систем уже появились права?
И этот язык, неизбежно, данные.А данных будет достаточно? Они не предполагают общей семантики? Вот вам данные: 124 343 32 23 123. Что я попросил вас сделать? :) Это не говоря о том, что и последовательность электрических сигналов превратить в «данные» без предварительной договоренности не выйдет. Так что общий язык не с данных начинается, увольте.
Это эквивалентно «запретить всем писать на не-ООП-языках». Спасибо, но нет.Почему же всем и так категорично? Я понимаю русский язык. Это не мешает мне худо-бедно понимать английский. И какой-нибудь JSON спокойненько передается по TCP/IP так же, как SNMP.
Вот только в интернете между узлами гуляют данные, а не объекты. Что как бы лишний раз подтверждает.Это только кажется, что проблему снимают именно данные. См. выше про общий язык. Это пока предпочитают об интерпретации данных договориться заранее и обходными маршрутами. Когда-нибудь придумают, как договариваться на ходу и по той же сети.
(На всякий случай напоминаю: мы же обсуждаем принципиальную невозможность.)
А потом транслирует на другую сторону, там сообщения принимают, обрабатывают (не зная, откуда они поступили), возвращают ответ, который транслируется назад, и там создается впечатление, что ответил мне тот объект, которому я послал исходное сообщение.
То есть это опять прокси, а не "передать полное состояние и сказать экземпляр какого класса нужно создать — если они «похожи» (тот же String, к примеру)".
Что это за право?
Мое право как разработчика использовать ту технологию, которую я считаю более оправданной для решения задачи.
Так что общий язык не с данных начинается, увольте.
Я не сказал, что он с них начинается, я сказал, что он ими заканчивается.
Почему же всем и так категорично?
Потому что для не-ООП-сервиса поддерживать ООП для сообщений нелогично.
Когда-нибудь придумают, как договариваться на ходу и по той же сети.
Или не придумают.
(На всякий случай напоминаю: мы же обсуждаем принципиальную невозможность.)
Я имею привычку считать, что пока возможность не доказана — ее и нет. Особенно учитывая количество вложенных усилий.
Вы предлагаете именно гомогенизировать среду. Это, конечно, похвально, но мне это банально не нравится.
То есть это опять прокси, а не «передать полное состояние и сказать экземпляр какого класса нужно создать — если они «похожи» (тот же String, к примеру)».Тогда я не понял, какой мы вариант обсуждаем, и в чем вы видите проблему?
Я не сказал, что он с них начинается, я сказал, что он ими заканчивается.Тогда надо определиться, где начало, а где конец :)
Вы предлагаете именно гомогенизировать среду. Это, конечно, похвально, но мне это банально не нравится.Я предлагаю подумать над возможностью динамически «учить» программные системы общаться на том языке, который удобен разработчику. В том числе и на объектном.
Я имею привычку считать, что пока возможность не доказана — ее и нет.К сожалению, эта привычка противоречит банальной логике. А оставаясь в ее рамках, …
… мы договорились: не определено, что опровергает исходный тезис.Когда-нибудь придумают, как договариваться на ходу и по той же сети.Или не придумают.
Тогда я не понял, какой мы вариант обсуждаем, и в чем вы видите проблему?
Я обсуждаю как раз вариант "создадим экземпляр класса на стороне получателя". Не прокси.
Я предлагаю подумать над возможностью динамически «учить» программные системы общаться на том языке, который удобен разработчику. В том числе и на объектном.
Динамически переключаться между stateful- и stateless-моделями? Свежо предание...
мы договорились: не определено, что опровергает исходный тезис.
Извините, но мы не договорились. Договоренность требует согласия двух сторон, а моего согласия вы не получили.
Вы пока так и не предложили решения, позволяющего гетерогенным системам иметь полностью униформное объектное поведение.
Я обсуждаю как раз вариант «создадим экземпляр класса на стороне получателя». Не прокси.Если объект не завязан на «контекст», который невозможно перетащить на другую сторону — никаких проблем не вижу. Если же таки завязан, то проксируем этот самый контекст.
Извините, но мы не договорились.Ну как же, вы же признали: «Или не придумают». То есть, допустили возможность? :) Ладно, я думаю мы здесь друг друга поняли и нет смысла дальше препираться. Вы отсутствие подтверждения существования считаете доказательством отсутствия. Соответственно, черных лебедей не существовало, пока их не обнаружили в Австралии (если не врут на этот счет). И Земля была плоской, пока не доказали, что она имеет форму апельсина :)
никаких проблем не вижу.
… а дублирование объекта?
Ну как же, вы же признали: «Или не придумают». То есть, допустили возможность?
Нет, не допускал. Я продолжаю считать, что идея тотальных объектно-ориентированных API невозможна (в гетерогенных средах), потому что какое-то API будет возвращать примитивные значения (иначе вы не сможете выполнить примитивные операции).
… а дублирование объекта?— деталь реализации, скрытая от пользователей (разработчиков) на обоих «концах».
Да как же она скрыта, если ее надо учитывать при программировании?
Учитывать надо только в момент создания объекта, представляющего удаленную сторону, что это именно удаленный объект (и соответствующим образом его сконфигурировать)…
"объект, представляющий удаленную сторону" — это обработчик уведомлений (и у него как раз прокси). А "раздвоившийся" объект — это пользователь, которого туда передали.
Повторюсь, проблема в том, что — в рамках распределенной системы — вы не знаете, с каким объектом вы работаете: локальным, удаленным (через прокси) или раздвоившимся.
«раздвоившийся» объект — это пользователь, которого туда передали.Он раздвоился только для наблюдателя со стороны. Для того, кто его передал, ничего не изменилось — объект как получал (откуда-то) сообщения, так и продолжает их получать. Для того, кому передали — то же самое: появился объект, я могу с ним работать как и с остальными; а где он на самом деле находится и как внутри устроен — мне «по барабану».
вы не знаете, с каким объектом вы работаете: локальным, удаленным (через прокси) или раздвоившимся«Не знаю и знать не хочу!» — основной принцип и признак «правильного» ООП :)
Вот только вы зря думаете, что вам по-барабану, где находится объект. Это известная ошибка в связи с разработкой распределенных систем — иллюзия, что можно работать с удаленным объектом как с локальным (наоборот — можно).
В частности, вы не знаете, в каких случаях — при одном и том же вызове — состояние объекта меняется, а в каких — нет. Вы, конечно, не хотите об этом знать, но это незнание приведет к прекрасным побочным эффектам.
Так потому и не сходится, что в момент перехода к распределенным системам инкапсуляция на сообщениях перестает работать. Что и говорит нам о плохой применимости ООП в этом месте.
Так потому и не сходится, что в момент перехода к распределенным системам инкапсуляция на сообщениях перестает работать. Что и говорит нам о плохой применимости ООП в этом месте.Тогда с этого места поподробнее: что значит «инкапсуляция перестает работать в распределенных системах». Особенно в контексте того же Erlang (который, как я понял, инкапсулирован «по самое не хочу»).
Так в Erlang то, что вы посылаете (сообщение) — не объект. И именно такая модель (системы — объекты, сообщения — нет) наиболее жизнеспособна.
У сообщения инкапсуляция минимальна. Его поведение тривиально, предсказуемо и известно снаружи.
Ход моей мысли очень прост: в распределенной системе есть неотъемлимая (inherent) сложность, которая приводит к тому, что использование объектов-с-поведением в качестве сообщений становится нерентабельно (как технически, так и когнитивно). Я не знаю ни одной акторной системы, где сообщения имели бы собственное поведение.
И это приводит к тому, что удобным униформным решением для API становится ситуация, когда системы являются объектами, а вот сообщения между ними — не являются.
проблема в том, что — в рамках распределенной системы — вы не знаете, с каким объектом вы работаете: локальным, удаленным (через прокси) или раздвоившимся.Они как-то связаны?
То есть, проблема именно в сообщениях?
Да, проблема именно в сообщениях.
Они как-то связаны?
Это одна и та же проблема.
Когда вы работаете с объектом-сервисом (т.е., отвечающим за API удаленной системы), у вас нет проблемы знать, какой он: он не может быть раздвоившимся (для сервисов это невозможно), а с локальным вы общаетесь так же, как с удаленным. Иными словами, вы всегда думаете, что этот объект удаленный, и эта мысль не нарушает процесс разработки.
А вот с сообщениями это не работает — если сообщение всегда удаленное, то вы не можете получить из него данные локально, и попадаете в замкнутый круг.
В какой замкнутый круг?
Да в простой.
Рано или поздно для какой-то операции вам нужны локальные данные. Не удаленный объект, которому вы послали сообщение, и он что-то сделал, а локально присутствующие данные. И либо у вас какие-то данные локальные, а какие-то — удаленные, и тогда у вас нет униформности, либо у вас все данные… какие?
Если следовать идее "все объект" — то да.
Там, где у вас одни данные — локальные, а другие — удаленные.
Ах если бы. Во-первых, у вас принципиально разная стоимость вызова (что ведет к chatty vs chunky). Во-вторых, вы не можете передать удаленный объект в произвольную систему (потому что вы не знаете, имеет ли эта произвольная система доступ к этому объекту).
И поведение этих объектов — именно благодаря реалистичным требованиям — будет разнородным. О чем и речь.
Вот только степень разницы, вызванная требованиями (в частности, как мы только что выяснили, [не]изменяемость состояния), намного выше, чем разница "отвечает на сообщение/или нет".
Собственно, если просто закрыть глаза на то, что DTO не имеют поведения — то да, все так и выглядит, "все есть объекты", только некоторые объекты не такие, как другие объекты.
«все есть объекты», только некоторые объекты не такие, как другие объекты.Почти все объекты — не такие (в том или ином смысле) как другие объекты. И существенно не такие. И при этом все они однородны — отвечают на сообщения.
И, кстати, оформилась хорошая мысль: можно «необъекты» присыпать сахарочком, чтобы они выглядели как объекты — путь мейнстрима; а можно «необъекты» реализовать в виде объектов — путь исходного ООП?
ссылку на объект «пользователь», по которой и реестр, и обработчик смогут послать любое сообщение, и получить ответ. Но вот незадача… ответ на сообщение getFullName — это что? Объект? Тогда мы попали в замкнутый круг. Или это значение-строка? Но тогда мы нарушили униформность.
Допустим, DCOM alike принцип. Если мы хотим униформности, то мы у себя генерируем объект вида «ответ» и пересылаем ссылку на него с запросом. Часть, ответственная за пользователя, генерирует полное имя и обращаясь к нашему объекту-ответу, устанавливает его внутреннее содержание.
И все равно, блин, «ссылка» будет примитивом.
Во-первых, ссылка будет примитивом. А во-вторых, если все объекты удаленные, то нам нужна сквозная доступность всех узлов от всех узлов.
Но ссылка да — будет примитивом. С другой стороны, это будет единственный примитив. Не то что бы мне это было интересно, просто к слову пришлось по ходу чтения вашей дискуссии.
Доступность предполагается. Необязательно всех, достаточно двух попарно обменивающихся частей системы, их специфических для данного вида обмена интерфейсов.
А вот нет, к сожалению. Вот пошел я в (удаленный) сервис, вызвал на нем операцию, хочу ее результат сохранить в (удаленную) БД. Сервис я вижу, БД я вижу, но друг друга они не видят. Как результат операции попадет в БД?
Если вы хотите DMA-alike поведение, то да, придется им друг друга увидеть.
Так что мы кинем в базу-то? Если единственное, что мы можем получить в виде результата — это ссылка?
Смотрите (давайте в обратном порядке, так немного очевиднее).
- Я иду в удаленную БД:
person = getPerson(id)
- Она вернула мне ссылку на объект на ее стороне (потому что ничего другого она сделать не может)
- В
person
именно эта ссылка - Я вызываю (удаленный) мейлер
sendEmail(person, text)
person
— это все еще ссылка на БД. Для мейлера она бессмысленна (у него нет доступа к БД)
Вы предлагаете в этот момент создать "у меня" проксирующий объект, внутри которого будет ссылка на объект "у БД", и ссылку на этот прокси передать в мейлер? Теоретически, это может работать, но (а) это будет гигантское размножение объектов и (б) теперь мейлеру, чтобы работать, нужно, чтобы и моя нода, и нода БД была онлайн (и каждая следующая нода будет добавляться в цепочку).
Смотрите (давайте в обратном порядке, так немного очевиднее).
Я иду в удаленную БД: person = getPerson(id)
Она вернула мне ссылку на объект на ее стороне (потому что ничего другого она сделать не может)
В person именно эта ссылка
Не совсем так, но даже в этом случае мы можем создать наш объект на пункте 0 и попросить его скопировать значение (состояние) объекта по ссылке person.
Я думал примерно о:
1. Создаем объект-ответ: textual_response = new TextualResponse;
2. Делаем запрос, передавая ссылку на наш объект: getPerson(id, textual_response)
3. База заполняет ответ на своей стороне: remote_host.textual_response.setvalue(...)
И далее. Технически будет обмен примитивами все равно — БД должна запихнуть строчку в наш textual_response объект, но это может быть что-то типа friend полей/методов, которые доступны только внутри объектов или при помощи 3й сущности-объекта, которая может работать с внутренним представлением и копировать состояние объектов.
… а еще я внезапно понял, что для этой системы невозможно реализовать равенство по значению. Или я что-то путаю?
Тут через 3й объект-супервизор см. выше.
Это длинная и нафиг ненужная цепочка, просто абстракция ради абстракции. Но раз уж мы говорим о том, что это-де технически невозможно, то все же можно придумать такую систему. Просто в качестве разминки ума, на деле кому это надо.
Не совсем так, но даже в этом случае мы можем создать наш объект на пункте 0 и попросить его скопировать значение (состояние) объекта по ссылке person.
Понимаете, вы предлагаете скопировать значения объекта. А что делать с поведением?
База заполняет ответ на своей стороне: remote_host.textual_response.setvalue(...)
В этот момент то, что будет внутри, придется передать как примитив (и по значению). То есть мы все равно нарушили идею о том, что все есть ссылка.
Понимаете, вы предлагаете скопировать значения объекта. А что делать с поведением?
Поведение у них по определению одинаково — это объекты одного типа. Я о конечных данных, если что, а не каких-нибудь active record'ах.
В этот момент то, что будет внутри, придется передать как примитив (и по значению). То есть мы все равно нарушили идею о том, что все есть ссылка
Не совсем. если мы делаем что-то типа objectA.cloneFrom(objectB), где объекты имеют доступ к внутреннему состоянию при клонировании, это будет внутренняя кухня, нас это не интересует — с точки зрения интерфейса у нас объект инициализировался объектом.
Поведение у них по определению одинаково — это объекты одного типа.
Ээээ, это только в том случае, если у нас с обеих сторон одна и та же среда выполнения.
Не совсем. если мы делаем что-то типа objectA.cloneFrom(objectB), где объекты имеют доступ к внутреннему состоянию при клонировании, это будет внутренняя кухня, нас это не интересует — с точки зрения интерфейса у нас объект инициализировался объектом.
Для этого cloneFrom
должен быть системным методом, недоступным для переопределения. Так?
Ээээ, это только в том случае, если у нас с обеих сторон одна и та же среда выполнения.
Необязательно. Допустим, у нас есть рамочная конвенция поведения, на которую мы расчитываем (одинаковый ответ на одинаковые воздействия).
Для этого cloneFrom должен быть системным методом, недоступным для переопределения. Так?
Допустим, базовый объект, самого нижнего уровня в системе, имеет cloneFrom изначально. Или он работает, например, через сериализацию по public properties.
Допустим, у нас есть рамочная конвенция поведения, на которую мы расчитываем (одинаковый ответ на одинаковые воздействия).
Для любого объекта? На любой реализации? Синхронно одинаковая везде? Утопия же.
Допустим, базовый объект, самого нижнего уровня в системе, имеет cloneFrom изначально.
Это и есть системный метод, причем недоступный для модификации, потому что он выражен не в тех терминах, в которых может говорить язык.
Или он работает, например, через сериализацию по public properties.
Не выйдет, потому что (а) публичных свойств регулярно недостаточно и (б) публичные свойства тоже могут возвращать только ссылки.
… а еще я внезапно понял, что для этой системы невозможно реализовать равенство по значению. Или я что-то путаю?
Где мне начинать использовать объекты, а где нет? Насколько это усложнит мою задачу (значительную часть времени я буду думать не о решении ее, а о там как получить нужные сущности)?Если исходить из определения «объект — это изолированный процесс со своей областью памяти» и учитывая, что в ФП никакого shared state не бывает в принципе, то все ваши вопросы выглядят надуманными и на практике возникают исключительно редко. По крайней мере за год программирования на Elixir у меня ни разу не было дилеммы «что делать процессом, а что нет». Т.е. ни на сколько это не усложнит вашу задачу, исходя из практики — только упростит.
Как мне не-объекты превратить в объекты и наоборот (когда надо)?Никогда не надо. Не бывает задачи превратить процесс в, допустим, список или наоборот — список в процесс.
Подскажите, где здесь ООП, а где ФП?
all_students = state
|> Map.keys
|> Enum.map(fn(grade) ->
[grade: grade, students: get_students_by_grade(state, grade)]
end)
Map
— это модуль в терминологии Elixir.
А приведенный вами фрагмент кода — полностью функциональный.
Впрочем если хотите некую композицию «объектов», то это тоже не вопрос, стартуйте нужный процесс из обработчика сообщения.
Плата за создание процесса не так велика, оверхэд примерно того же порядка как при создании объектов в Java.
В общем, смысл в том, чтобы в качестве объектов рассматривать только те сущности, которые реально живут какой-то своей жизнью. У которых есть понятный жизненный цикл или миссия «бесконечно» обрабатывать некоторые запросы.
Всё остальное — это просто данные, которые обрабатываются при помощи ФП.
I made up the term “object-oriented”, and I can tell you I didn't have C++ in mind
Видимо, даже внеся значительный вклад в «изобретение будущего», предсказать его все равно невозможно.
в котором ООП дошло до того, что даже операторы — это чьи-то методыПоясните, пожалуйста, что вы имели ввиду?
[1, 2, 3, 4, 5].<<(2.+(2.*(2))) # => [1, 2, 3, 4, 5, 6]
Собственно, это еще раз про чистоту, которую мы обсуждаем в соседней ветке, если я правильно понял? Тогда данную можно закрыть.
Ну есть у вас сущность и набор операций над ней, причем тут ООП?
biological scheme of protected universal cells interacting only through messages that could mimic any desired behavior
Тем более мейстрим-реализации ООП свели ценность этой аналогии к нулю, не обеспечивая реальную защиту внутреннего состояния и увлёкшись странной идеей, что ядро клетки — это тоже клетка, митохондрия — это тоже клетка, рибосома — тоже клетка и т.д… Но нет, внутреннее состояние клетки — это не набор других клеток. В биологии у вас не получится спуститься на уровень кварков и заявить, что кварк — это тоже клетка :-)
My biology major had focused on both cell metabolism and larger scale morphogenesis with its notions of simple mechanisms controlling complex processes and one kind of building block able to differentiate into all needed building blocks.
Вообще, интересная манера: схватиться за одну фразу (к тому же метафорическую) и на ней строить какие-то теории, ссылаясь на автора фразы, но при этом игнорируя 99,999% его труда. Тогда и статью надо назвать: «Как выглядит метафора клеток в функциональном языке»
У Вас свои стойкие предубеждения и Вы интерпретируете всё в пользу Вашей точки зрения. Вплоть до того, что «initial premises in designing the Smalltalk interpreter» внезапно превращаются в определение ООП. И все, кто не согласен, с Вашей интерпретацией книги про раннюю историю Smalltalk, сразу признаются ничего не понимающими в ООП, похоже, включая и самого Алана )))
Вот его мнение от 2010 года в тему этой статьи:
Many of Carl Hewitt’s Actors ideas which got sparked by the original Smalltalk were more in the spirit of OOP than the subsequent Smalltalks. Significant parts of Erlang are more like a real OOP language than the current Smalltalk, and certainly the C based languages that have been painted with “OOP paint”.пруф
Кроме того, я-то как раз не считаю C#, Java или даже Smalltalk идеальными выразителями идей OOP. Если вы (или кто-то еще) покажете эти самые «parts of Erlang», которые «more like a real OOP language than the current Smalltalk» — я буду вам (или кому-то еще) очень признателен. Серьезно. Но пока что как-то не получилось. Может быть, в данном случае, потому что вы уделяете внимание каким-то не тем вопросам? В общем, из наших с вами дискуссий мне пока не удалось понять, чем же Erlang лучше (проще, мощнее) того же Smalltalk-а. Но это, разумеется, мои проблемы.
Ну, и еще один вопрос. Где и кого я лишь на основании несогласия с моей точкой зрения назвал ничего не понимающим в ООП?! Если такое было, я приношу тому человеку свои глубочайшие и искренние извинения. Вот, мне интересно, только честно: до наших с вами дискуссий вы много времени посвятили тому, чтобы вникнуть в суть ООП и его настоящие базовые идеи?
Вот ещё Вам цитата из совсем свежего:
Что Вы ещё хотите понять про Erlang/Elixir в плане ООП, мне уже непонятно. Для полноценного сравнения со Smalltalk, если Вы его ждёте, мне пришлось бы освоить Smalltalk. Поэтому я сравниваю их с языками, которые мне уже хорошо известны, типа Ruby или C#. И они в плане соответствия идеям Кэя сильно проигрывают Elixir, хоть и достаточно полно (особенно Ruby) реализуют «Всё есть объект».
Вот, мне интересно, только честно: до наших с вами дискуссий вы много времени посвятили тому, чтобы вникнуть в суть ООП и его настоящие базовые идеи?Достаточно много. Где-то 3 года назад заинтересовался истоками ООП.
Просто меня удивляет, как Вы интерпретируя и опираясь на то, что пишет Кэй, приходите к несогласию с ним же.А в чем несогласие-то?
Что Вы ещё хотите понять про Erlang/Elixir в плане ООП, мне уже непонятно.Ничего страшного — мне тоже непонятно. :) Просто вы попробовали. Для кого-то этого было достаточно. А мне вот оказалось мало. Мое понимание ООП не изменилось, и в него сказанное не вкладывается. Нет, я честно пытался не «победить в споре», а понять вашу точку зрения. Но «не сходится». Smalltalk (с поправками на реальную жизнь) — он сходится. А здесь — пока нет.
Про сравнение со Smalltalk, разумеется, и речи нет. Возможно (нет, точно!), это мне надо взять и попробовать плотно Erlang. А если вопрос был не риторическим, то хорошей заманухой был бы показ интересных, нетривиальных задач с красивым ООП-решением. А то ведь (вы помните, исходное обсуждение в нашем чате?) даже ваши коллеги по Erlang-у выражали сильное недоумение по поводу ООП-программирования на это языке.
А в чем несогласие-то?Ну вот Вы говорите:
Соответственно, речь таки не идет об ООП в исходном «Кэевском» смысле слова. Получается, скорее, некая имитация «антуража» ООП…
А он про то же самое говорит, что это один из двух путей понять «real OOP». По-моему тут явное противоречие Вашей точки зрения с точкой зрения Кэя.
показ интересных, нетривиальных задач с красивым ООП-решениемну, это надо что-то из open source посмотреть, не писать же нетривиальный проект чисто в демо-целях )))
даже ваши коллеги по Erlang-у выражали сильное недоумение по поводу ООП-программирования на это языке.Не забывайте, что имеет место сильная зашоренность на тему ООП. Для многих ООП == [«Инкапсуляция», «Наследование», «Полиморфизм»]. Для некоторых вообще ООП — это там где классы. В этом плане я согласен, с nwalker, когда говоришь ООП, никогда точно не знаешь, что подумал собеседник.
Заметим, кстати, что в модели акторов есть и инкапсуляция, и полиморфизм.
Ну вот Вы говорите:Не, вы, пожалуйста, не путайте, и не перемещайте слова из одного контекста в другой. Я охотно допускаю, что Erlang с моделью акторов может развивать идею ООП или, по крайней мере, помогать ее лучше понять. Этот язык давно стоит в моем списке на изучение… слишком давно, к сожалению. И мне очень интересно, что там Кэй обнаружил (это интервью я читал почти сразу после его появления), и посмотреть действительно ли это круто на практике. Другое дело: я не смог этого понять из вашей статьи. Собственно, я уже об этом писал.
Соответственно, речь таки не идет об ООП в исходном «Кэевском» смысле слова. Получается, скорее, некая имитация «антуража» ООП…
А он про то же самое говорит, что это один из двух путей понять «real OOP». По-моему тут явное противоречие Вашей точки зрения с точкой зрения Кэя.
Планка того как может работать ООП после Smalltalk-а поднята довольно высоко. И, к примеру, чистота там играет довольно существенную роль. По крайней мере, очень часто наиболее красивые и мощные решения получаются там, где «все есть объект». И напротив: много проблем возникает именно там, где от этого принципа отходят. А уж после мейнстримовых языков к отрицанию данного принципа относишься оооочень настороженно :)
Не забывайте, что имеет место сильная зашоренность на тему ООП.Именно против этой зашоренности я и возражаю! И поймите мое этакое разочарование(: вы начинаете с разбора «истоков», что просто замечательно, но в итоге скатываетесь туда же, куда и мейнстрим, да еще и подгоняете цитаты и определения под это дело. Я прекрасно понимаю, что и меня обвиняют в том же ;) И мне данная дискуссия помогла понять, что в отличие от споров с поклонниками триады «инкапсуляция–наследование–полиморфизм» (которые зачастую даже не хотят четко сформулировать свою позицию), мы как раз сходимся в том, что ООП шире и мощнее, чем попытка его пародии в мейнстримовом сознании. Пытаться трактовать цитаты в данном случае — скорее всего, путь в никуда. Тем более, ограничиваясь только цитатами Кэя — учитывая, что он таки не один работал над проблемой… Судя по всему (или даже наверняка), даже в коллективе разработчиков того же Smalltalk были разные точки зрения.
В общем, если вы примите совет, то я бы на вашем месте попробовал найти хорошие примеры использования «силы» :) настоящего ООП в Erlang-е.
На практике зачастую вырождающаяся в «объекты ради объектов» и «чистоту ради чистоты». Как говорится, гладко было на бумаге, да забыли про овраги.
Однако что одна, что другая идея на практике не реализуема на 100%. Программа без побочных эффектов никому не нужна. То же самое и с объектами, программа, которая работает только с объектами — это вещь в себе, она не пишет в файлы, не рендерит странички в html, не принимает пользовательский ввод, не пишет в БД (хотя может ООСУБД ещё кто-то использует?) и т.д.
Наличие хотя бы автораспаковывания/упаковывания данных необходимо для всех вышеперечисленных задач.
:) Вы меня реально за идиота принимаете? Уже не в первый раз притом :) Вы действительно считаете, что я не понимаю, что рекурсия где-то должна кончится? что не имею представления о том, как работает современный компьютер? что не понимаю, что рекурсия должна где-то закончится?
Да мне не интересно, что там «внутри»Вот! Именно это я Вам вчера и писал!
ООП не интересует что там внутри. В нашем случае внутри акторов: ФП и обычные типы данных.
Даже когда буду не то что коллекцию обрабатывать, а банальный цикл или даже «if» писать — все равно буду объектам слать сообщения.И какому же объекту уйдёт ваше while или if сообщение? Я понимаю, что можно фантазию подключить и выдумать вспомогательные объекты. Вопрос только нафига?
Вы меня реально за идиота принимаете? Уже не в первый раз притом :) Вы действительно считаете, что я не понимаю, что рекурсия где-то должна кончится?Я просто не понимаю, почему Вы считаете принципиальным на каком моменте эта рекурсия закончится.
Вот на мой взгляд, добавление к числу объектного поведения капитально усложняет работу с числами, зачем тогда мне рекурсивно превращать их в объекты?
Попробуйте в ООП-стиле написать код для вычисления суммы квадратов натуральных чисел от 1 до n. Для примера реализация на Elixir:
Enum.reduce(1..n, &(&1*&1 + &2))
ООП не интересует что там внутри.<…>Я просто не понимаю, почему Вы считаете принципиальным на каком моменте эта рекурсия закончится.У меня есть ощущение, что вы просто не хотите это понять :) Могу ошибаться. Но все же очень просто. Есть ведь разница между «меня не интересует» и «ООП не интересует»? Я сомневаюсь, что ООП обладает сознанием, но даже если так, то со мной своими переживаниями не делится. А вот меня действительно мало интересует устройство объектов — до тех пор, пока они остаются объектами и мне не приходится постоянно переключать «режим» сознания при работе с сущностями различного рода. Понимаете? Граница проходит либо по языку либо «где-то за ним» — и это существенно. В первом случае мне приходится разговаривать на двух языках одновременно — и это сложнее, чем на одном (во втором случае). В Smalltalk-е граница совсем чуть-чуть залезает в язык, что изредка мешает.
И какому же объекту уйдёт ваше while или if сообщение?
(someObject checkCondition) ifTrue: […] ifFalse: [].
[someObject checkCondition] whileTrue: […].
Соответственно, if уходит экземпляру подкласса Boolean (там отдельно есть True и False), а while — блоку.Попробуйте в ООП-стиле написать код для вычисления суммы квадратов натуральных чисел от 1 до n.(1 to: n) inject: 0 into: [:sum :each | sum + each squared]
Для примера реализация на Elixir:Вы уверены, что это сработает правильно? Видимо, в Smalltalk reduce работает иначе, приходится фокусничать:Enum.reduce(1..n, &(&1*&1 + &2))
(0 to: n) reduce: [:a :b | a + b squared].
Enum.reduce(1..n, 0, &(&1*&1 + &2))
чтобы без фокусов )))
Есть ведь разница между «меня не интересует» и «ООП не интересует»? Я сомневаюсь, что ООП обладает сознанием, но даже если так, то со мной своими переживаниями не делится.Не придирайтесь… Это всего лишь сокращение для «Во время применения ООП нас не интересует»
мне не приходится постоянно переключать «режим» сознания при работе с сущностями различного рода. Понимаете?Честно говоря, не понимаю в чём проблема. Мы в жизни постоянно переключаемся между сущностями различного рода, можно сказать, это дефолтное восприятие человека. Вы по-любому переключаетесь, когда говорите о массивах, строках, числах. Просто делаете вид, что это типа тоже объекты. Если бы можно было бы оставаться всегда на уровне объектов, не переключаясь, Вы бы названия обычных типов данных вообще забыли бы.
(0 to: n) reduce: [:a :b | a + b squared].Кхм, и в каком месте тут униформность?
если вызов метода записывается как
object method: arg
то что такое квадратные скобки? что такое a + b? что такое b squared? что такое ":a :b |"?
Даже реализация такого микропримера совсем не униформна, о каком отсутствии «переключений» тогда вообще говорить…
что такое a + b?Объекту в a посылается сообщение + b.
что такое b squared?Объекту в b посылается сообщение squared.
что такое квадратные скобки?Синтаксический сахар для создания объекта, представляющего лексическое замыкание.
что такое ":a :b |"?Синтаксический сахар для помещения в лексическое замыкание объектов, представляющих переменные с соответствующими именами.
Я уже много раз говорил: Smalltalk не идеален с точки зрения чистоты ООП (да и с некоторых других точек зрения). И, к примеру, последний синтаксис реально выбивается из концепции посылки сообщений — в процессе программирования это прерывает поток.
Но: 1) все то же самое можно (несколько длиннее) написать на «чистых» сообщениях; 2) все то же самое (с некоторым усложнением парсера и, возможно, ВМ) можно реализовать примерно на таком же синтаксисе при сохранении «чистых» сообщений.
Просто Smalltalk в какой-то момент оказался (о чем жаловался тот же Кэй) сложным и соответственно инертным в развитии. Есть веские причины полагать, что основным виновником стала как раз обсуждаемая тема: от объектов отказались слишком рано на пути от верхних уровней абстракции к нижним уровням реализации. Сделать это пришлось, как я понимаю, главным образом в угоду производительности. Не забываем — это середина и вторая половина 70-х с соответствующими скоростями и объемами памяти в компьютерной технике. Скажем, уже в Self (середина 80-х) от части таких проблем уже удалось уйти. Думаю, если бы мейнстрим и массовые сознание не властвовали над нами, давно уже могло бы развиться и дальше.
То, что теоретически что-то возможно, не делает это автоматически удобным для всех. C Lisp похожая ситуация, концепция изящна и удивительно проста. Но для легкости восприятия требуется либо отходить от привычного мышления, либо увешиваться синтаксическим сахаром.
Поэтому ни Smalltalk, ни Lisp и не захватили мейнстрим… среднестатистическому программисту банально не удобно с любой униформностью. Да и в физической реальности нас окружает наблюдаемое многообразие, а униформность остаётся теоретической концепцией.
Понимаете, наличие синтаксического сахара говорит о том, что концепция в чистом виде не слишком удобна и/или трудна для понимания.… или для реализации.
И тут встаёт выбор «вам шашечки (чистоту концепции) или ехать (возможность эффективно решать задачи)».Если бы тут было «исключающее или», мы бы до сих пор программировали на ассемблере. Вы продолжаете настаивать на «чистоте» как некоторой никому не нужной блажи. Из ваших высказываний получается, что логика такая: нет систем, которые бы я знал и где соблюдался этот принцип => принцип не нужен (да и не реализуем). Если вы так и не видите язъяна здесь, что ж… я признаю свою бессилие :)
Поэтому ни Smalltalk, ни Lisp и не захватили мейнстрим… среднестатистическому программисту банально не удобно с любой униформностью.Давайте не будем касаться это темы: вы не в курсе, почему Smalltalk «не завхватил мейнстрим», да и «среднестатистический программист» не излагал вам свои проблемы.
… или для реализации.В Lisp же принцип реализован, можно униформно программировать на голом AST, а синтаксический сахар всё равно добавляют.
Из ваших высказываний получается, что логика такая: нет систем, которые бы я знал и где соблюдался этот принцип => принцип не нуженНе совсем, во-первых системы есть. А во-вторых, логика такая: увеличение уровня чистоты принципа не даёт соразмерного увеличения практической пользы. И даже наоборот отход от чистоты частенько позволяет решать задачи эффективнее.
К примеру,
b squared
— чище, b * b
— понятнееa +: b
— чище (только не работает почему-то),a + b
— понятнее.К примеру, b squared — чище, b * b — понятнееНу, напишите там b * b — это тоже сообщение. Только squared как раз показывает намерение и понятнее, вы же сами сформулировали: «сумма квадратов», а не «сумма произведений на самого себя».
увеличение уровня чистоты принципа не даёт соразмерного увеличения практической пользыОт принципа, я так понимаю, это по-вашему не зависит?
Ну, напишите там b * b — это тоже сообщение.Я хотел написать
b *: b
, чтобы униформно было, но оказалось, что так нельзя (Только squared как раз показывает намерение и понятнее, вы же сами сформулировали: «сумма квадратов»С этим согласен… Только вот мне интересно, для кубов тоже библиотечный метод есть? )
От принципа, я так понимаю, это по-вашему не зависит?Это уже философия… но вообще да, если принцип направлен на предельную минимизацию кол-ва концепций.
Потому что на каком-то этапе наступает конфликт с другим принципом: «Всё должно быть так просто, как только возможно, но не проще»
Я хотел написать b *: b, чтобы униформно было, но оказалось, что так нельзя (В синтаксисе Smalltalk-а предусмотрено три вида сообщений (в их названиях можно проследить функциональные корни, кстати — получатель считается как аргумент): унарные (без аргументов; squared — как раз пример такого), бинарные (ровно один аргумент; + и * — примеры) и «с ключевыми словами» (сколько угодно аргументов; 1 to: 10 do: [] — два аргумента: 10 и []).
А что значит «униморфно»? Не знаю такого слова.
Только вот мне интересно, для кубов тоже библиотечный метод есть? )Может и есть — зависит от реализации, я точно не помню. Но вся фишка в том, что если «нету, а нужен», то просто берете и добавляете. Мы этого, вроде как, даже не касались? «Все объект» — означает в том числе и то, что я могу изменить в системе все что угодно. В том числе «библиотечные» объекты. Но вы же скажете, что это не нужно…
«Все объект» — означает в том числе и то, что я могу изменить в системе все что угодно.
Э нет. Можно иметь систему, в которой все объект, но при этом ничего не мочь изменить. А можно иметь систему, в которой вообще нет объектов, но мочь изменить что угодно. Это не связанные вещи.
Можно иметь систему, в которой все объект, но при этом ничего не мочь изменить.Зависит от нашего желания. Если у нас «все объект», и мы заложили в понятие объекта (не в смысле определения) возможность менять его поведение (что логично для системы, в которой мы собираемся что-то разрабатывать), то мы сможем это делать с любым объектом (если, опять же — по своему желанию — не захотим это запретить). На случай если захочется здесь развернуть дискуссия — я не собираюсь в этот тезис «упарываться». По факту в (более-менее) «нормальных» объектных системах (Smalltalk, Self — как минимум) это так.
Зависит от нашего желания.
Вот именно, что зависит. И если у нас было такое желание, то мы его реализовали, и с постулатом "все есть объект" оно не связано.
. Если у нас «все объект», и мы заложили в понятие объекта (не в смысле определения) возможность менять его поведение (что логично для системы, в которой мы собираемся что-то разрабатывать
А вот для меня это не логично. Я хочу, чтобы части системы были стабильны, и их нельзя было менять.
Я хочу, чтобы части системы были стабильны, и их нельзя было менять.Ну и не меняйте.
Нельзя было.
… вот и нет возможности менять поведение любого объекта.
Вот только у меня нет проблемы: я не считаю, что отсутствие возможности поменять стандартную библиотеку — проблема.
Ну и да, напомню, что это обсуждение началось с того, что тезис "все есть объект" означает возможность что угодно поменять. Согласитесь, что это верно только в том случае, если вы можете поменять любой объект, а не всегда.
А вот если вам вдруг покажется логичным сделать один объект с «базовой функциональностью», включающей возможность создавать новые объекты с отличным от исходного поведением (а значит, менять их) — исходный тезис вдруг может показаться более осмысленным. Но я не настаиваю.
… а если мне покажется логичным сделать один модуль с базовой функциональностью, включающей возможность создавать новые модули с отличающимся поведением — то у меня "все есть модули", а возможность изменения осталась.
Вообще, связывать ООП и легкость повторного использования — это как раз та ловушка, которая приводит к "ООП — это наследование".
А что значит «униморфно»? Не знаю такого слова.А почему Вы меня об этом спрашиваете? Я это слово не употреблял.
я могу изменить в системе все что угодно. В том числе «библиотечные» объекты. Но вы же скажете, что это не нужно…Мне этого в Ruby хватило выше крыше. Поверьте, на практике при разработке крупных проектов это ни фига не круто.
Прошу прощения, «обчитался» :)А что значит «униморфно»? Не знаю такого слова.
А почему Вы меня об этом спрашиваете? Я это слово не употреблял.
Поверьте, на практике при разработке крупных проектов это ни фига не круто.«Вы просто не умеете их готовить» :) Либо зря обобщаете свой негативный опыт Ruby.
Тесты — ok, допустим у нас 200% покрытия и они даже упали:
1) Как быстро понять, что дело именно в переопределении? Вы ведь не пишете тесты на стандартную библиотеку и на все зависимости.
2) Как быстро найти какая из зависимостей в этом виновата?
Далеко не везде можно переопределить любой метод/функцию из чужой библиотеки, не прибегая к форкам и пулл-реквестам )Везде есть код, который не контролирует автор и который при обновлении может сломать то, что автор написал.
1) Как быстро понять, что дело именно в переопределении? Вы ведь не пишете тесты на стандартную библиотеку и на все зависимости.Тест есть на поведение моего объекта, непосредственно зависящего от не моего объекта.
2) Как быстро найти какая из зависимостей в этом виновата?Я сталкивался с таким когда-то в VisualWorks — там (насколько я помню) приходилось искать. Но метод же — объект. Нет проблем протащить туда информацию о том, откуда растут ноги. Так ли это в Pharo — надо уточнить. Почему-то больших проблем с этим не возникало — может из-за (меньшего) размера сообщества, может из-за того что не злоупотребляют.
Честно говоря, не понимаю в чём проблема. Мы в жизни постоянно переключаемся между сущностями различного рода, можно сказать, это дефолтное восприятие человека.Вот, как раз некоторые считают, что дефолтное состояние человека — воспринимать окружающий мир и все сущности в нем как объекты: идентифицировав объект, мы начинаем пытаться на него воздействовать и смотреть, что же будет в ответ. Вроде как это более общая метафора, чем выполнение операций (из наперед заданного, строго ограниченного множества), поскольку я могу попытаться что угодно сделать с объектом — нет ограничений по возможным операциям. Все это спорно, конечно…
Вы по-любому переключаетесь, когда говорите о массивах, строках, числах. Просто делаете вид, что это типа тоже объекты. Если бы можно было бы оставаться всегда на уровне объектов, не переключаясь, Вы бы названия обычных типов данных вообще забыли бы.Я переключаюсь только между ожидаемым поведением разных объектов и его соответствием тем задачам, которые я хочу решить. Я не переключаюсь между разными способами общения с объектами. Если возникает необходимость так делать, приходится отвлекаться — поток прерывается — решать задачу становится сложнее и дольше. Да, большинство из нас привыкло работать в таком режиме — это не значит, что такой режим лучший.
После долгого (многомесячного, скажем) программирования на Java (где тоже постоянно приходится скакать, так как сам язык «мало-объектный») к Smalltalk привыкаешь минут за 15 и с удовольствием. После даже часового программирования на Smalltalk обратно к Java привыкаешь гораздо дольше и, что самое противное, с неудовольствием. И сразу чувствуешь, как падает производительность — постоянно с объектного мышления по схеме «найти подходящий объект и передать ему нужное сообщение» приходится перепрыгивать на языковые конструкции. Если «к хорошему привыкаешь быстро» говорят не зря, то становится понятным что есть хорошо, а что не очень.
Но, да — это мои личные ощущения. Более объективных обоснований (кроме попыток формально «поймать простоту» — но там пока мне самому не все ясно) у меня нет. Так что, разумеется, есть полное право не соглашаться и продолжать совмещать «типы и объекты». А для меня этот момент является хоть и не самым сильным, но все же аргументом против подробного знакомства с Erlang для изучения ООП.
Вот, как раз некоторые считают, что дефолтное состояние человека — воспринимать окружающий мир и все сущности в нем как объекты: идентифицировав объект, мы начинаем пытаться на него воздействовать и смотреть, что же будет в ответ.Как минимум, мы отличаем объекты с поведением (акторы) от объектов без поведения (данные) и принципиально по-разному к ним относимся. Благодаря чему у нас не возникает дилеммы «человек пишет ручкой» или «ручка пишет, используя человека».
Как минимум, мы отличаем объекты с поведением (акторы) от объектов без поведения (данные) и принципиально по-разному к ним относимся. Благодаря чему у нас не возникает дилеммы «человек пишет ручкой» или «ручка пишет, используя человека».Ручка у вас — это данные?
Если вы не против, то давайте все-таки уточним набор свойств, а так же что понимается под типом?
А по поводу набора свойств, чем Вас не устраивает предложенный? Для большинства реальных применений ручек указанного набора свойств вполне хватает. Естественно, у неё как у любого физического объекта очень много свойств: масса, объём, длина, диаметр корпуса, цвет корпуса и т.д. Но это всё очень редко имеет значение. Так что «тип и цвет чернил» вполне достаточный набор.
Попробуйте в функциональном стиле описать работу с пользовательским интерфейсом. Я понимаю, тут постановка задачи — показать несостоятельность ООП подхода, но, например, плюсы
Number Range::reduce(Range range, std::function[Number(Number,Number)],Number init = Number(0)) {
Number val(init); for (i: range) val = function (val, ш); return val;
}
//struct fn { Number operator() (Number a, Number b) {return a + b*b;} };
auto fn = [](Number a,Number b){return a + b*b;};
Range::reduce(Range(1,100),fn); // fn());
Очень лаконично всё описывают.
Я понимаю, тут постановка задачи — показать несостоятельность ООП подходаНет, Вы вообще не поняли, что мы в этой ветке обсуждаем (невозможность униформности на всех уровнях системы), и даже статью, видимо, не дочитали.
но, например, плюсыВ Вашем примере куча не-ООП… 0 — это не объект в плюсах, for — не метод, +, * и даже return — это тоже не методы. Так что на униформную ООП-реализацию это точно не тянет.
Очень лаконично всё описывают.А вот это Вы зачётно пошутили )))
Вот на мой взгляд, добавление к числу объектного поведения капитально усложняет работу с числами, зачем тогда мне рекурсивно превращать их в объекты?
Очень не хотел влезать в спор, но все же хочу кое-что сказать по этому поводу. Лично для меня, придание примитивным типам объектности, всегда имело смысл не в том, чтобы, собственно, работать с примитивными типами, как с объектами, а в том, чтобы иметь возможность работать с объектами, как с примитивными типами. Просто если в языке нет разницы между примитивным типом и объектом (а основной смысл добавления объектности к примитивному типу в том, чтобы стереть эту разницу), то я могу создать свой тип, который со стороны сможет вести себя так же как, например, дробное число (реализовывать все его интерфейсы), а внутри все будет устроено так, как мне надо. Вот не понравилось мне, что есть только float и double. Мало мне показалось. Написал свой тип и назвал triple. И он ничем не «хуже» встроенных типов. Язык получается расширяемым. Можно так же добавить quadruple, quintuple и т.д. пока не надоест.
Что касается усложнения примитивных типов, то лично я его не заметил. Серьезно, я ни разу не замечал, чтобы мне, например, в C# делать арифметику со встроенными типами было сложнее, чем в Си. Внутри типы может быть и сложнее устроены, но мне-то, как разработчику, пишущему на этом языке, что с того? Я работаю с ними не внутри, а снаружи, как с черными ящиками. Зато, если понадобится использовать нестандартный числовой тип, то в том же Си будет существенное синтаксическое отличие. Вот решили мы отказаться от чисел с плавающей точкой, и до последнего все расчеты делать в простых дробях. По сути, все операции над пользовательской структурой (числитель/знаменатель) придется делать с помощью функций. А в C# достаточно заменить тип у переменных, а все остальное останется как было. Куда уж проще? Я не говорю, что без ООП подобного не добиться. И даже не говорю, что подобный функционал необходим. Просто не вижу проблем от того, что простые типы стали объектами. Зато у нас появляется унификация. Все типы равны между собой. И даже встроенные типы не «равнее» пользовательских. По-моему это здорово.
… вот только для описанного вами не надо, чтобы примитивные типы были объектами. Надо, чтобы в языке было как можно меньше операций, на которые вы не можете повлиять. В частности, в вашем случае вы можете определить математические операции (но, к сожалению, не операции в модуле Math
) не только для встроенных математических типов, но и для своих.
Все типы равны между собой.
Вот в C#-то это точно не так. Чего стоит одна только разница между value types и reference types.
Лично для меня, придание примитивным типам объектности, всегда имело смысл не в том, чтобы, собственно, работать с примитивными типами, как с объектами, а в том, чтобы иметь возможность работать с объектами, как с примитивными типами.Хорошее дополнение в дискуссию. На мой взгляд, для расширения примитивных типов хватит и структур, над которыми имеется возможность определить любые операторы. Так ведь?
я ни разу не замечал, чтобы мне, например, в C# делать арифметику со встроенными типами было сложнее, чем в Си.Загляните в IL-код, там нет никаких объектов для чисел. Но не задумываясь о разнице между числами и объектами, Вы легко можете написать код, где в цикле возникнет какой-нибудь box int32. И удар по производительности обеспечен. Смысл не в том, что нельзя работать с числами как с объектами. Смысл в том, что нельзя забывать, что число — это число, а объектом оно только притворяется. Поэтому переключение сознания между объектами и примитивными типами данных неизбежно для написания нормального кода.
Вот на мой взгляд, добавление к числу объектного поведения капитально усложняет работу с числами,Забыл уточнить: на основании чего вы это утверждаете?
… если про это не знать, то можно получить много прекрасных побочных эффектов, начиная с уже описанной невозможности блокировки.
Если обсуждали, повторюсь: «побочные эффекты» (в широком смысле слова) являются следствием неудачной реализации; при их обнаружении — поправьте ее так, чтобы поведение объектов было ожидаемым. Не всегда это просто. Более того, допускаю, что это и не всегда возможно — но это не доказано. Я на практике таких примеров не встречал.
при их обнаружении — поправьте ее так, чтобы поведение объектов было ожидаемым
Легко сказать.
d = a
c = a add b
Чему равно d
?
d = a
c = a + b
А теперь?
А вот и нет Если при d = a
происходит копирование, а при a + b
— увеличение a
, то d
не равно a
.
Впрочем, более интересный вопрос состоит в том, равны ли d
и c
.
А вот и нетТак вы пример чистого ООП рассматриваете? У вас d = a — это сообщение = a, посылаемое d? Вы бы формулировали вопросы как-то поаккуратнее и точнее что ли? А то сложно следить за полетом вашей мысли. И создается впечатление, что вы стараетесь просто «подловить» собеседника.
В этом случае вообще ничего не гарантируется, да. Одни сплошные «побочные эффекты» в широком смысле этого слова. Но вы-то говорите о тех из них, которые приводят к неправильному поведению софта, не так ли? Придется еще подбирать правильные объекты (которые не будут копировать, когда требуется в переменную положить объект).
У вас d = a — это сообщение = a, посылаемое d?
А это не принципиально. Это может быть как сообщение, так и поведение языка.
В этом случае вообще ничего не гарантируется, да. Одни сплошные «побочные эффекты» в широком смысле этого слова. Но вы-то говорите о тех из них, которые приводят к неправильному поведению софта, не так ли?
Так побочные эффекты формата "изменилось состояние объекта, от которого я этого не ожидал" — как раз самые страшные (и, кстати, именно поэтому в функциональном программировании так любят неизменное состояние).
А это не принципиально. Это может быть как сообщение, так и поведение языка.Если это обычное присваивание, то d = a.
Так побочные эффекты формата «изменилось состояние объекта, от которого я этого не ожидал» — как раз самые страшные (и, кстати, именно поэтому в функциональном программировании так любят неизменное состояние).Ну, да. Проблема известная — надо придумывать, как ее решать. Например, использовать объекты, которые не изменяют свое состояние, если это не нужно.
Если это обычное присваивание, то d = a.
А что такое "обычное присваивание"? Оно для всех объектов работает одинаково?
Например, использовать объекты, которые не изменяют свое состояние, если это не нужно.
Воот. Теперь какие-то объекты у нас меняют состояние, а какие-то — нет (иногда еще и при одних и тех же операциях).
А что такое «обычное присваивание»? Оно для всех объектов работает одинаково?«Обычное присваивание» обычно означает помещение в объект, представляющий переменную с именем слева от «обычного присваивания», ссылки на объект, представляющий результат вычисления выражения справа от него же. Кстати (упреждая возможные скучные вопросы), в Smalltalk-е (в отличие от Self-а) присваивание — это не сообщение.
Воот. Теперь какие-то объекты у нас меняют состояние, а какие-то — нет (иногда еще и при одних и тех же операциях).У нас всегда одни объекты меняли свое состояние, а другие — нет. Даже при одних и тех же сообщениях. Даже одни и те же объекты при одних и тех же сообщениях.
У нас всегда одни объекты меняли свое состояние, а другие — нет. Даже при одних и тех же сообщениях. Даже одни и те же объекты при одних и тех же сообщениях.
Вы не поверите, как это ужасно звучит для человека, попробовавшее функциональное программирование и чисто immutable-программы. Намного страшнее, чем "не все — объекты".
А еще меня успокаивает, что в реальной-то жизни (по крайней мере как я ее воспринимаю) любое воздействие на объект может изменить его состояние — но ничего, как-то оно не падает с Runtime Exception (или я этого не замечаю)… :)
|a b c d|
a := 1.
b := 2.
d := a.
c := a + b.
d. "-> 1"
|a b c d|
a := OrderedCollection newFrom: #(1).
b := OrderedCollection newFrom: #(2).
d := a.
c := a addAll: b.
d. "-> an OrderedCollection(1 2)"
«Take care that addAll: also returns its argument, and not the receiver!»
С чего это вдруг?
Исправленная версия:
|a b c d|
a := 1.
b := 2.
d := a.
c := a + b.
c. "-> 3"
d. "-> 1"
|a b c d|
a := OrderedCollection newFrom: #(1).
b := OrderedCollection newFrom: #(2).
d := a.
c := a addAll: b; yourself.
c. "-> an OrderedCollection(1 2)"
d. "-> an OrderedCollection(1 2)"
|a b c d|
a := OrderedCollection newFrom: #(1).
b := OrderedCollection newFrom: #(2).
d := a.
c := a addAll: b.
d = a. "-> true"
«Но не в обоих случаях d равно первоначальному значению a.»
А почему объект 1
, получив сообщение + b
, не меняет свое состояние?
Я дополнил своим уточнением…
А то, что объект может изменить свое состояние, получив сообщение, вроде бы и так очевидно?Это да. И это показывает, что числа — это не совсем объекты даже в Smalltalk. На самом деле закос под объекты очень сильно увеличивает когнитивную нагрузку при программировании.
Для сравнения, в Elixir акторы при получении сообщения могут менять своё состоянии. А данные — наоборот всегда неизменны.
P.S. Ну а в ответ на сообщение addAll вернуть аргумент — это вообще саботаж )))
это показывает, что числа — это не совсем объекты даже в SmalltalkМожет не означает обязан. Существует масса объектов, не изменяющих свое состояние. Я вообще склоняюсь к тому, чтобы не упоминать про состояние (внутреннюю память объекта) в «универсальной» части определения ООП.
И я никак не пойму, что же вас не устраивает в числах-объектах?
На самом деле закос под объекты очень сильно увеличивает когнитивную нагрузку при программировании.Что такое когнитивная нагрузка? Обращаясь к объекту, вы каждый раз стараетесь вспомнить/понять, изменит объект свое состояние или нет? Если так, то зачем?
Для сравнения, в Elixir акторы при получении сообщения могут менять своё состоянии. А данные — наоборот всегда неизменны.
Может не означает обязан. Существует масса объектов, не изменяющих свое состояние.Вы правы, не обязан. Только в этом и есть когнитивная нагрузка — каждый раз вспоминать:
* какие объекты мутабельны, какие иммутабельны
* на какие сообщения объект вернёт yourself, на какие один из аргументов сообщения, а на какие вернёт новый объект того же типа, что и получатель сообщения
Получается, что на практике никакого единообразия и в помине нет. Хотя мы ещё даже не добрались до сообщений, результат обработки которых нам не нужно ждать. В Smalltalk есть такие? Или поток выполнения всегда ждёт ответа на сообщение?
И я никак не пойму, что же вас не устраивает в числах-объектах?То, что они только маскируются под объекты.
каждый раз вспоминать:Я как раз выше спросил: зачем это нужно каждый раз знать?
* какие объекты мутабельны, какие иммутабельны
* на какие сообщения объект вернёт yourself, на какие один из аргументов сообщения, а на какие вернёт новый объект того же типа, что и получатель сообщенияА когда вы вызываете функцию, вы не должны учитывать, что она вернет?
То, что они только маскируются под объекты.Что вы имеете ввиду? Напомню: это int где-нибудь в Java «маскируется» под объект; а числа в Smalltalk-е являются объектами.
Я как раз выше спросил: зачем это нужно каждый раз знать?Мутабельный объект может пройти по цепочке вызовов и измениться где-то глубоко в недрах кода. Хотя вызывающий код может этого совсем не ожидать.
Если же у нас все аргументы функций иммутабельны, то эта проблема отпадает в принципе.
А когда вы вызываете функцию, вы не должны учитывать, что она вернет?С функциями всегда очевидно, что она вернёт результат вычисления на базе переданных аргументов.
Напомню: это int где-нибудь в Java «маскируется» под объект; а числа в Smalltalk-е являются объектамиНеа, даже технически экземпляры SmallInteger в Smalltalk не совсем объекты.
Ну а уж концептуально, числа вообще не могут быть объектами, т.к. не обладают собственным поведением.
Мутабельный объект может…Это же не повод пытаться полностью просчитывать все возможные последствия от каждой строчки кода? Есть масса вещей, которые могут заставить систему отработать не так, как мы ожидали. Наверное, даже в Erlang-е или даже в самом Haskell-е?
Понятно, что хочется свести это к минимуму. Но есть два пути: стараться как можно больше запретить и «не пущать», а можно не париться и дать возможность пользователю (разработчику) самому сформировать стратегию и тактику написания надежного кода. Сейчас преобладает первая точка зрения, отсюда вся это шумиха вокруг статической типизации и чистого ФП… Но не доказано (по крайней мере пока), что это единственный или более правильный подход. Мне как-то ближе второй.
С функциями всегда очевидно, что она вернёт результат вычисления на базе переданных аргументов.С методом тоже очевидно: он вернет результат вычисления на базе переданных аргументов и своего состояния, возможно изменив свое состояние :)
Неа, даже технически экземпляры SmallInteger в Smalltalk не совсем объекты.Поясните.
Ну а уж концептуально, числа вообще не могут быть объектами, т.к. не обладают собственным поведением.Откройте протокол класса Magnitude или его подкласса и убедитесь, что многие люди считают иначе :)
Откройте протокол класса Magnitude или его подкласса и убедитесь, что многие люди считают иначе :)
Сколько из этих методов меняют внутреннее состояние объекта, представляющего число?
а можно не париться и дать возможность пользователю (разработчику) самому сформировать стратегию и тактику написания надежного кода.«Не париться» при таком подходе у Вас не получится, ну или надёжный код не получится )
Потому что для надёжности Вам придётся держать в уме детали реализации каждого используемого метода, как бы он там что не изменил.
С методом тоже очевидно: он вернет результат вычисления на базе переданных аргументов и своего состояния, возможно изменив свое состояние :)Так вот нифига. Я выше уже писал про OrderedCollection>>addAll, который с какого-то перепугу тупо вернул свой аргумент, а не результат вычисления. А на тему «возможно изменив свое состояние», зачем я должен об этом думать, когда можно принципиально разделить эти 2 варианта на уровне языка?
Откройте протокол класса Magnitude или его подкласса и убедитесь, что многие люди считают иначе :)Ну открыл, вижу что эти люди и с SRP кардинально не согласны :)
А вот примеров объектного поведения я там не вижу. Все эти методы можно было бы распределить в несколько подходящих модулей. И это было бы лучше, чем «свалка» в классе Number.
«Не париться» при таком подходе у Вас не получится, ну или надёжный код не получится ) Потому что для надёжности Вам придётся держать в уме детали реализации каждого используемого метода, как бы он там что не изменил.«Не париться» — относилось к системным программистам (разработчикам языка, в частности) и подразумевало «не стараться быть умнее всех разработчиков и не пытаться наставить их на путь истинный, запрещая делать неправильные вещи».
А по поводу связи между «надежным кодом» и «держать в уме» — очень спорно, но мне уже не хватает ни времени ни сил развивать эту тему здесь.
Я выше уже писал про OrderedCollection>>addAll, который с какого-то перепугу тупо вернул свой аргумент, а не результат вычисления.Авторы метода посчитали, что будет удобнее сделать результатом вычислений добавляемую коллекцию. Я не знаю почему. Возможно, первым пользователям метода было так удобнее, а потом сработал принцип «здесь так принято». По крайней мере, беглый просмотр «посыльщиков» #addAll: дает несколько примеров collection addAll: elements; yourself и ни одного использования аргумента.
Но я хочу обратить внимание на другое обстоятельство. ООП предлагает следовать следующей концепции: раз объект сам решает как обработать сообщение, то нельзя жестко «закладываться» на предположения о том, как этот объект обработает сообщение. Вроде бы очень алогичная вещь: как мы вообще можем писать программы в таких условиях?!? И у вас постоянно проскальзывает это недоумение… Но ведь пишут :) И довольно неплохо получается… Предлагаю обдумать этот момент с непредвзятых позиций. Правда, не уверен, что стоит обсуждать это именно здесь.
Ну открыл, вижу что эти люди и с SRP кардинально не согласны :)Может, и не согласны — в том смысле, в котором этот принцип трактуете вы. Или есть какие-то другие, более важные принципы, в свете которых SRP — опять же — принимает несколько иное «звучание». Меня лично идеи дяди Боба не очень вдохновляют.
Все эти методы можно было бы распределить в несколько подходящих модулей.А они разделены: на категории, на пакеты… На самом деле, это вопрос о соотношении «определяющих принципов» (которые часто очень хочется высечь в камне — и сделать себе жизнь проще, потому что можно будет не думать), и практики (которая зачем-то все время опровергает все эти принципы). Кент Бек в Smalltalk Best Practice Patterns представил концепцию Talking Programs, проиллюстрировав ее прекрасным и очень простым примером.
Это очень интересная тема для обсуждения, но (как я уже писал где-то рядом) я не выдерживаю данный формат дискуссии — надо куда-то ее переносить.
Возможно, первым пользователям метода было так удобнее, а потом сработал принцип «здесь так принято»О том и речь. Когда можно сайд-эффекты замутить над примитивными данными — это рано или поздно приводит к WAT.
Вроде бы очень алогичная вещь: как мы вообще можем писать программы в таких условиях?!? И у вас постоянно проскальзывает это недоумение…У меня? У меня на эту тему никакого недоумения нет, потому как процессы в Elixir действительно принимают сообщения, и тот, кто посылает сообщение, даже не догадывается какой функцией оно будет обработано и когда (можно задать только таймаут, после которого сообщение теряет актуальность).
А вот в Smalltalk это не так, насколько я понял из PBE. Там посылка сообщения от вызова метода отличается только в теоретическом плане. На практике сообщение обрабатывается одноименным методом и технически от вызова метода это ничем не отличается. Да есть детали реализации, о которых они пишут, типа методы класса-экземпляра. Но фундаментально это ничего не меняет.
Хотя может я что-то недопонял… Буду благодарен, если Вы продемонстрируете практическое отличие сообщений в Pharo от вызова метода в Ruby.
я не выдерживаю данный формат дискуссии — надо куда-то ее переносить.придётся всё-таки митап по ООП организовывать )))
Когда можно сайд-эффекты замутить над примитивными данными — это рано или поздно приводит к WAT.А в других случаях WAT просто исключен, да?
посылка сообщения от вызова метода отличается только в теоретическом плане. На практике сообщение обрабатывается одноименным методом и технически от вызова метода это ничем не отличается.И опять по кругу? Мы уже даже здесь выясняли, чем даже «будничное» связывание по имени метода отличается от вызова метода — ключевые слова: «позднее связывание». Это если не учитывать возможность обработать сообщение, для которого у объекта метода нет вообще. Например, таким образом очень просто и красиво реализуются Mock-и. Подозреваю, что в Ruby присутствуют аналогичные механизмы.
А в других случаях WAT просто исключен, да?Нет, конечно. Какие-то WAT есть практически в любом ЯП, но больше всего их от сайд-эффектов и от неявного приведения типов.
ключевые слова: «позднее связывание»Так позднее связывание сейчас очень во многих ЯП есть. И возможность «обработать сообщение, для которого у объекта метода нет вообще». Точнее метод то есть, он просто работает как fallback, в Ruby он method_missing называется. А в целом это обычный dynamic dispatch.
Я ожидал от Smalltalk гораздо большего в этом плане…
Я ожидал от Smalltalk гораздо большего в этом плане…Не знаю, что именно ожидалось :) Smalltalk — это не только язык. Но и язык в своем роде уникален. Собственно, я с другой стороны на это дело смотрю: я ожидал (бы) большего от (ООП-)языков, которые были созданы 10 – 15 – 20 – … лет после Smalltalk. Что, собственно, было существенно хорошего добавлено?
Поясните.
LargePositiveInteger new. "-> 0"
SmallInteger new. "-> Error: SmallIntegers can only be created by performing arithmetic"
Ну и Formal Specification of the Primitive Methods заодно.
P.S. Чем больше читаю про Smalltalk, тем сильнее у меня ощущение, что Вы рассказывали про какой-то другой ЯП, потому что в этом униформности точно нет. Не то что бы в этом есть что-то плохое, но это факт.
У меня никак не получается сформулировать общее понимание вашего непонимания :) Оно раз за разом повторяется в рамках одного и того же паттерна, но я никак не могу это дело «схватить» и сформулировать… Сюда относятся и утверждение про числа-необъекты, и про примитивы и отход от объектов «внутри», и возврат «не результата вычислений», и что-то еще там в других комментариях… и, судя по всему, эта самая «униформность»… Давайте попробуем через частности:
— Что вы понимаете под униформностью?
— Где я говорил про униформность Smalltalk? Или в какой части мои «россказни» про Smalltalk не соответствуют действительности?
Нежелание класса отвечать на какое-любо сообщение не делает его экземпляры не объектами
Если объект не отвечает ни на одно сообщение, остается ли он объектом?
5 == 5.
"--> true"
(1/5) == (1/5).
"--> false"
LargePositiveInteger new == LargePositiveInteger new.
"--> false"
Что вы понимаете под униформностью?Единообразие, как концептуальное, так и синтаксическое. И как следствие отсутствие неожиданного поведения на уровне языка и стандартной библиотеки.
Хороший пример униформности — Scheme, вот ранее обсуждаемая задачка на Racket Scheme:
(define (sum-squared-up-to n)
(foldl
(lambda (x acc) (+ (* x x) acc))
0
(range 1 (+ n 1))))
Синтаксис абсолютно однороден. Концепция чистая, даже без сахара. Всё есть список. Первый элемент списка — имя вызываемой функции, остальные — аргументы для неё.
Где я говорил про униформность Smalltalk?Хм, а что мы тогда тут обсуждаем? Насколько я уловил Вашу позицию: «В Elixir униформности нет, не то что в Smalltalk, а как же жить без униформности возможно»
Не, ну это не просто нежелание отвечать на какое-то сообщение, это не желание инстанцироваться так же как концептуально аналогичный класс.Инстанцирование — это всего лишь результат (или сайд-эффект) посылки сообщения.
Они ещё и сравниваются по значению, а дроби уже по ссылке.В обоих случаях сообщение == означает сравнение, как вы пишите, по ссылке (сравнение на идентичность). Просто в одном случае (с дробями) в ответ на сообщения возвращается один и тот же объект (что логично: число 1/5 как объект существует в единственном экземпляре), а в другом — разные (поскольку вы просите создать новый экземпляр, передавая сообщение new). Никакого нарушения изначальных договоренностей здесь не наблюдаю: в ответ на сообщение объект возвращает какой-то объект, не оговаривается будет это новый или созданный когда-то ранее. Ясно, что в реальной жизни нужны оба варианта. В чем проблема?
Просто в одном случае (с дробями) в ответ на сообщения возвращается один и тот же объект (что логично: число 1/5 как объект существует в единственном экземпляре)Было бы логично, только в том то и фишка, что возвращаются разные.
Обратите внимание, что 1/5 не равна 1/5 в Smalltalk, несмотря на то, что в данном случае это рациональные дроби, а не десятичные.
… а что будет, если мы заменим 1/5
на 1*5
?
(1*5) == (1*5) "--> true"
кажется, я совсем запутался с == в Smalltalk :facepalm:
Если поинтересоваться реализацией SmallInteger
(который как раз типичный value type), то ничего странного в этом true
нет. Лишнее подтверждение идеи о том, что все абстракции текут.
(10 factorial) == (10 factorial) "--> true"
(15 factorial) == (15 factorial) "--> false"
Хотя я нашёл всё-таки метод сравнения по значению:
(10 factorial) = (10 factorial) "--> true"
(15 factorial) = (15 factorial) "--> true"
Странно, что эта несогласованность даже глубже, чем я предполагал:
Опять-таки, ничего странного. "Smalltalk never overflows", так что результат factorial
может иметь разные типы в зависимости не только от типа получателя, но и от его значения.
Но да, для людей с привычкой к статической типизации (включая меня) звучит дико.
По поводу факториала:
10 factorial class. "-> SmallInteger"
15 factorial class. "-> LargeInteger"
Понятно, что Smalltalk в этом не виноват, но сейчас = для сравнения редко, где используется.
А абстракция действительно протекает… SmallInteger и LargePositiveInteger ведут себя совсем по-разному и при этом между ними есть неявное приведение типов.
А абстракция действительно протекает… SmallInteger и LargePositiveInteger ведут себя совсем по-разному и при этом между ними есть неявное приведение типов.Какая именно абстракция протекает? Кто запрещает объектам разных классов (да даже одного класса) вести себя по-разному? Приведения типов — это ваша интерпретация. (Вообще, в Smalltalk-е есть ли типы? «Строгая динамическая типизация» есть, а типов вроде как и нету?:) По факту, есть создание в процессе обработки сообщения объектом некоторого класса экземпляра другого класса. Вы же не это называете «протеканием абстракции»?
Я правильно понял, что привычные «операторы» сравнения никогда не сравнивают по значению в Smalltalk? А иллюзия сравнения по значению возникает только в случаях, когда по факту имеется один объект для обеих сторон сравнения.
Единообразие, как концептуальное, так и синтаксическое. И как следствие отсутствие неожиданного поведения на уровне языка и стандартной библиотеки.Вы действительно считаете, что первое (униформность) гарантирует второе (отсутствие неожиданного поведения)? В программах на Scheme не бывает багов?
Синтаксис абсолютно однороден. Всё есть список. Первый элемент списка — имя вызываемой функции, остальные — аргументы для неё.Функция — это тоже список? Да, она задается списком, но это же объект другого рода? И кто-то (кстати кто? это тоже исключение из концепции?) ведь превращает список в функцию? В одних случаях. А в других — в вызов функции. А еще, как я понимаю, тут тоже есть примитивные конструкции, которые не совсем соответствуют исходной концепции? Мне это все кажется полной аналогией того же Smalltalk (что и понятно — последний делался по образцу Lisp-а). Я ошибаюсь?
Хм, а что мы тогда тут обсуждаем? Насколько я уловил Вашу позицию: «В Elixir униформности нет, не то что в Smalltalk, а как же жить без униформности возможно»Самое время (спустя много дней и массы букв) это выяснить :)
На данный момент я наверное так сформулирую свою позицию. Мне (1) нравятся максимально простые и при этом максимально мощные концепции; (2) хочется перенести такую концепцию в максимально чистом (по крайней мере, в неиспорченном) виде в язык.
Концепция, в которую вовлечено два компонента (объект и сообщение) скорее всего проще, чем та, в которой присутствует большее количество. Про мощность — разговор отдельный, но по моим ощущениям ООП не менее мощная, чем ФП, так как в объектах можно смоделировать любую концепцию ФП; к примеру, на том же Smalltalk-е можно построить (был реализован, насколько я знаю) интерпетатор того е Lisp-а.
Не уверен, возможно ли в принципе абсолютно выполнить пункт (2) — подтверждающих примеров нет. Smalltalk, хоть он и далек от совершенства, является одним из лучших на сегодняшний компромиссов между этим желанием и реальностью. При этом (на мой взгляд) в Smalltalk-е не «коверкуются» те интенции, которые стояли за ООП (насколько я смог их уловить).
(Если посмотреть, к примеру, на публикации автора — многое станет понятно)
гарантирует второе (отсутствие неожиданного поведения)? В программах на Scheme не бывает багов?А как отсутствие неожиданного поведения связано с отсутствием багов? С неожиданным поведением только часть багов связана, причём чаще меньшая, т.к. программисты привыкают к «особенностям» используемого языка.
Функция — это тоже список? Да, она задается списком, но это же объект другого рода?Определение функции — это список с вызовом функции define. Да, разумеется, есть с десяток функций, которые реализуются внутри интерпретатора, но для программиста они ведут они себя точно так же, как и все остальные функции.
тут тоже есть примитивные конструкции, которые не совсем соответствуют исходной концепции?да вроде нет, можно пример?
Ваша позиция понятна, но всё-таки заметно что Вы ей следуете, исходя из того, что неравнодушны к Smalltalk. Это создаёт некоторую предвзятость.
Концепция, в которую вовлечено два компонента (объект и сообщение) скорее всего проще, чем та, в которой присутствует большее количество.Вот это не факт. Простота применения ЯП на практике с количеством компонентов в концепции слабо коррелирует.
Есть, например, концепция с нулём компонент — «всё есть ничто» и даже соответствующий ЯП. А вот решить на нём практическую задачу совсем не просто :-D
по моим ощущениям ООП не менее мощная, чем ФП, так как в объектах можно смоделировать любую концепцию ФПOMFG, зачем? Эти парадигмы совершенно ортогональны. Моя позиция в том, что они должны применяться комплементарно на разных уровнях абстракции. ООП можно применять и в Erlang/Elixir и в диалектах Lisp, но там, где оно реально имеет смысл и пользу. А протаскивание идей ООП через все уровни абстракции вплоть до самых примитивных типов ведёт к излишнему и неоправданному усложнению системы.
Собственно изначальная статья посвящена тому, что «Здравствуй ФП» не обозначает «Прощай ООП», оно означает «Здравствуй нормальное ООП, теперь ты будешь работать для меня, а не наоборот»
А как отсутствие неожиданного поведения связано с отсутствием багов?Бага — неожиданное (для разработчика) поведение системы. Нет?
Про примитивы Scheme — это был вопрос. Я с ним очень мало имел дело и практически ничего не помню. Просто википедия подсказывает про «минимум примитивных конструкций», я тоже понимаю что без них никуда…
Но вот списки и функции — они «униформность» ведь не нарушают? Я так понимаю, для вас несколько «параллельных» терминов/понятий в рамках одной концепции — это ОК?
Ваша позиция понятна, но всё-таки заметно что Вы ей следуете, исходя из того, что неравнодушны к Smalltalk.«Неравнодушие» к Smalltalk-у определяется тем, что это самая лучшая из известных мне систем, реализующих концепцию и хоть в какой-то степени годных к практическому использованию. Не наоборот.
Да это не факт — поэтому я и написал «скорее всего». Но совсем по другой причине: система, состоящая из N подсистем, может оказаться сложнее системы, состоящей из N+M, если связи между подсистемами первой окажутся сложнее, чем связи во второй. А концепция «с нулем компонент» (с одной все таки?) — действительно проще. Другое дело — возможности, которые она дает. Поэтому я писал и про «мощность».Концепция, в которую вовлечено два компонента (объект и сообщение) скорее всего проще, чем та, в которой присутствует большее количество.
Вот это не факт.
Моя позиция в том, что они должны применяться комплементарно на разных уровнях абстракции.Это я уже давно понял. Но наши позиции как раз тоже не противоречат друг другу. ООП не предполагает как таковой отказ от всего другого. Просто все другое можно реализовать как объекты. Подозреваю, кстати, что ФП — аналогично. А вопрос — в простоте исходных положений. Вам это не интересно, а я считаю, что именно оттуда «растут ноги» у избыточной сложности.
Бага — неожиданное (для разработчика) поведение системы. Нет?Необязательно, большинство багов встречаются в бизнес-логике или в логике самого программиста (алгоритмов, которые он реализует). В общем, только часть багов связана с неожиданным поведением языка и его стандартной библиотеки.
Но вот списки и функции — они «униформность» ведь не нарушают? Я так понимаю, для вас несколько «параллельных» терминов/понятий в рамках одной концепции — это ОК?Это лишь для большей понятности. По факту, любой список — это вызов функции, т.е. это не разные понятия, а одно и то же разными словами. Даже «списки-литералы» записываются как вызов функции list:
(list 1 2 3)
А вопрос — в простоте исходных положений. Вам это не интересно, а я считаю, что именно оттуда «растут ноги» у избыточной сложности.
Это всё очень субъективно… Краткость исходных положений не означает простоту. На мой взгляд «всё есть объект» — это очень сложное исходное положение, как в реализации, так и в следовании ему и главное — не отражающее реальное положение дел ни концептуально, ни технически. Т.е. если пытаться следовать этому тезису абсолютно, то вся система будет построена вокруг этой идеи, а не с её помощью.
По факту, любой список — это вызов функции(1 2 3) — какая функция будет вызвана?
application: not a procedure;
expected a procedure that can be applied to arguments
given: 1
- Определение функции — это список.
- Вызов функции — это список.
Определение функции — это вызов функции define.
А вот любой вызов функции — это уже список )))
Тут по факту всё действительно униформно, т.к. для большинства интерпретаторов/компиляторов (вне зависимости от ЯП) программа — это AST, а тут мы упрощаем работу парсеру, по сути сразу записывая всё в виде AST.
Также очевидно, что по мощности этот подход мощнее любого другого варианта высокоуровневого языка, т.к. в других концепциях мы можем получить только подмножество возможных AST, разрешенное языком, а тут полное множество.
(1 2 3) — это список в синтаксисе Scheme?Ответ:
Просто список, не являющийся вызовом функции, записать нельзя.То есть, это таки список, но записать его нельзя? При этом
Определение функции — это список.То есть, список записать нельзя, а частные случаи списков — можно?
Вызов функции — это список.
Я понимаю, что если не задумываться сильно и не придираться, то это все можно принять. Примерно так же как и «в Smallltalk-е все делается через сообщения, кроме…»
Поэтому если нужен список, как структура данных, а не как «управляющая конструкция», то вызываем функцию list, ну или какой-нибудь генератор списков. Это и есть униформность, мать её xD
Это всё очень субъективноЯ как раз пытаюсь рассуждать (хоть и на довольно профанско-бытовом уровне) о сложности объективно…
На мой взгляд «всё есть объект» — это очень сложное исходное положение, как в реализации, так и в следовании емуКак это дело реализовывать и ему следовать — вопрос отдельный. Само утверждение «все сущности в системе являются A» делает систему простой по сравнению с системой, где часть сущностей является А, другая — B, а еще бывают C и D и т.д. Безотносительно к сути A. Работать с группой людей или с группой кошек не сложнее, чем работать с группой, где вперемежку идут люди, обезьяны, тигры, носороги и ядовитые змеи (сам не пойму, почему родилась такая аналогия). Или взять для примера конвейер… Нет?
На самом деле, нет. Работать с группой людей проще, чем работать с группой "животных" (где среди животных есть люди и носороги). И работать с группой людей и группой носорогов по отдельности проще, чем работать с группой людей и носорогов.
Вы хотите сказать, у вас все типы одинаковы? Или же они все разные, с разным набором операций и т.д.? Если так, то по факту у вас те же тигры, обезъяны, змеи и носороги — вперемежку. А еще — отдельно — люди. Но отдельно — условно. Потому что в какой-то момент вам все равно придется вспоминать, что люди — тоже животные и складывать их в ту же кучу. Но это внутри. А снаружи все эти животные выглядят как люди. Так получается?
Вы хотите сказать, у вас все типы одинаковы?
Нет, не хочу.
Если так, то по факту у вас те же тигры, обезъяны, змеи и носороги — вперемежку.
Это почему вдруг?
Потому что в какой-то момент вам все равно придется вспоминать, что люди — тоже животные и складывать их в ту же кучу.
Ключевой вопрос: а зачем?
Это почему вдруг?Потому что это разные типы.
Ключевой вопрос: а зачем?В какой-то момент вам же может захотеться внутри какого-нибудь актора-объекта-человека (где у нас только животные вроде как?) поработать с коллекцией других акторов-объектов-людей?
Потому что это разные типы.
И как из этого вытекает, что они вперемешку? Они разные, я и могу их отдельно держать и обрабатывать.
В какой-то момент вам же может захотеться внутри какого-нибудь актора-объекта-человека (где у нас только животные вроде как?) поработать с коллекцией других акторов-объектов-людей?
Извините, я запутался в вашем предложении. Почему внутри "человека" только "животные"? Я вообще не понимаю, что такое "внутри" в этом контексте.
И как из этого вытекает, что они вперемешку? Они разные, я и могу их отдельно держать и обрабатывать.У вас отдельно функции, которые работают только с целыми числами, отдельно — только с плавающей запятой и совсем отдельно — со строками? :)
Извините, я запутался в вашем предложении.Ну, это вы переиначили изначальный смысл метафоры. Я попытался развить уже вашу.
У вас отдельно функции, которые работают только с целыми числами, отдельно — только с плавающей запятой и совсем отдельно — со строками?
У меня могут быть такие функции.
Ну, это вы переиначили изначальный смысл метафоры. Я попытался развить уже вашу.
В моей метафоре нет понятия "внутри".
Извините, я запутался в вашем предложении.Вопрос, могут ли быть другие. Если да — где же пресловутая униформность?
В моей метафоре нет понятия «внутри».Это понятие есть в контексте применения данной метафоры.
Вопрос, могут ли быть другие. Если да — где же пресловутая униформность?
Другие кто? Другие функции? Да, могут. Униформность, например, там, где все целые числа ведут себя одинаково.
(еще она там, где для любого сложения достаточно написать +
, и результат всегда будет относиться к той же группе, что и оба операнда)
Это понятие есть в контексте применения данной метафоры.
Я не очень понимаю, где вы взяли этот контекст.
Само утверждение «все сущности в системе являются A» делает систему простойНет. Простой она станет, если все сущности действительно являются А. Но по факту мы имеем систему «Давайте будем считать, что все сущности являются А».
… вот только это "общее" для разных задач может быть разным.
Всё равно подгоним их под общность?
Почему оно есть — см. выше: программист должен иметь возможность взаимодействовать (использовать) с любой компонентой в системе (напрямую или опосредованно). Необходимость этого самого взаимодействия — и есть то самое общее.
Вопрос, однако, в том, должно ли это взаимодействие быть одинаковым для всех компонент в системе. Если нет, то между этими компонентами, на самом деле, общего все-таки нет.
Ответ же на этот вопрос ("должно ли...") в свою очередь, упирается в то, эффективна ли такая унификация с точки зрения разработки (и с точки зрения реализации средств разработки, и с точки зрения нагрузки на программиста, ими пользующегося).
(Грубо говоря, человек должен воспринимать и свет, и химический состав (вкус). Но что общего между светом и химическим составом — кроме того, что мы решили, что их надо воспринимать? Можем ли мы построить униформную модель обработки света и химического состава только опираясь на то, что человек воспринимает и то, и другое? Будет ли эта униформная модель нам полезна?)
Я же специально написал: "должно" определяется по эффективности унификации. Я не вижу объяснения, почему вы считаете, что унификация "все есть Х" эффективна.
В целом, я чувствую, что дискуссия постепенно теряет смысл — то ли мы добрались до неких базовых (мировоззренческих) вещей, где «на вкус и цвет», то ли просто потеряли все нити :) …Если будет время, я просмотрю затронутые темы и еще раз постараюсь их осмыслить.
Например, чтобы не создавать несколько параллельных механизмов взаимодействия среды с разнородными сущностями — для каждого вида сущностей отдельно.
… вы считаете, что создать один механизм, который будет реализовывать все те же варианты внутри себя, по каким-то причинам эффективнее?
Само взаимодействие-то не униформно, мы это уже выяснили.
Само взаимодействие-то не униформно, мы это уже выяснили.Во-первых, мы это не выяснили — хотя бы потому, что слово «униформный» в приемлемом виде никто так не определил. Вы им жонглируете как хотите.
вы считаете, что создать один механизм, который будет реализовывать все те же варианты внутри себя, по каким-то причинам эффективнее?На это уже тоже несколько раз отвечалось. Я не знаю, эффективнее или нет — зависит от критериев эффективности и способов их измерения. А вот с точки зрения пользователя-человека не только я один, а очень многие люди считают, что сокрытие деталей — таки удобнее и делает работу с системой проще (является средством борьбы со сложностью). Думаю, вы знаете многих из этих людей, читали их книжки…
Собственно, наши линии рассуждений более-менее вырисовываются. Мы обсуждаем некую (абстрактную) сложную систему. Вы говорите: эта сложность ей присуща по природе, мы с ней ничего сделать не можем кроме как отразить в неизменном в виде в наших инструментах и дальше мучится с ней всегда. Я же пытаюсь отстоять право людей, которые допускают что это сложность исходит (всего лишь) из нашего восприятия, найти такой способ восприятия этой системы, который данную сложность снимет. Вам почему-то эти попытки очень не нравятся. Нет?
А вот с точки зрения пользователя-человека не только я один, а очень многие люди считают, что сокрытие деталей — таки удобнее и делает работу с системой проще (является средством борьбы со сложностью).
Ну во-первых, я не понимаю, как от "сокрытия деталей" вы переходите к "все есть Х".
А во-вторых...
Вот смотрите, есть два контейнера, словарь (в значенении key-to-value map) и репозиторий (в значении repository pattern). И есть у них у обоих операция get(key)
(мы для простоты будем считать, что в репозитории ключ всегда один, благо, к этому действительно можно свести).
Посмотрим на словарь. Что возвращает get
? По идее, то, что раньше положили с помощью set
. Что возвращает get
, если с таким ключом раньше ничего не клали? Что будет, если мы эту "деталь" — поведение контейнера в заданных условиях — скроем от пользователя? Станет ли его жизнь легче?
Давайте посмотрим, какие вообще варианты у нас есть. Вариант первый: в ответ на get
с несуществующим ключом возвращать "ничего" — проще говоря, null
. Но что делать, если кто-то хочет положить к нам null
? Запрещать? Наверное, нет. Значит, первый вариант откладываем. Второй вариант: возвращать не null
, а специальный notfoundobject
. Теперь мы не можем работать со словарем, не зная этой "детали" — потому что notfoundobject
не будет вести себя так, как то, что мы раньше положили в словарь. Третий вариант — возвращать пару из (найдено, значение) — нарушает правило "что положили, то и достали" и усложняет код (всегда нужны проверки). Наконец, четвертый вариант — возвращаем то, что положили, если не найдено — бросаем ошибку. Уф, этот вариант самый простой (кроме тех, кто не любит бросаемые ошибки как класс).
А теперь репозиторий. Что возвращает get
, если с таким ключом ничего не найдено?.. А вот репозиторий может возвращать null
, потому что, согласно принятым соглашениям, положить в репозиторий null
— нельзя, так что здесь наш выбор сводится к одному из двух (первому или четвертому сценарию из описания словаря). И снова, станет ли пользователю легче, если эта "деталь" — то, какой вариант мы выбрали — будет от него скрыта?
Ну и наконец, предположим, что разработчики словаря и репозитория выбрали разные варианты (это вполне возможный сценарий, прямо скажем, потому что разные варианты использования). Теперь мы не можем сказать, что "деталь", какой именно контейнер к нам пришел, может быть от нас скрыта — потому что их поведение различается.
Все это призвано проиллюстрировать одну простую вещь: не каждое сокрытие деталей делает работу с системой проще.
Вы говорите: эта сложность ей присуща по природе, мы с ней ничего сделать не можем кроме как отразить в неизменном в виде в наших инструментах и дальше мучится с ней всегда.
Нет, я этого не говорю. Я говорю, что есть больше одного варианта работы с этой сложностью. Впрочем, что еще важнее, вы и я считаем "сложностью" разные вещи, и то, что вы считаете сложностью, и с чем вы хотите бороться и снимать, я считаю упрощением.
Ну во-первых, я не понимаю, как от «сокрытия деталей» вы переходите к «все есть Х».Вы контекст моего сообщения помните? Уже не первый раз вы начинаете не очень адекватные ветки, начиная обсуждать вырванные из контекста слова. Мы обсуждали следующее. В программной среде (как и в реальном мире) мы имеем дело с множеством разнородных сущностей (опять пришлось возвращаться назад и заменять слово «объект» на «сущность» — к чему бы это?). Вопрос состоит в том, стоит ли эту разнородность транслировать в нетронутом виде в язык или попытаться представить единый способ взаимодействия с этими сущностями. Поскольку взаимодействовать (создавать, удалять, просматривать, изменять… вооще, каким-то образом воздействовать) придется, я (хотя идея мне и не принадлежит) предположил, что искомое «общее» между разнородными сущностями как раз в наличии этого взаимодействия и состоит. А вот то, что разных сущностей будет много — это уже как раз детали.
<…>Все это призвано проиллюстрировать одну простую вещь: не каждое сокрытие деталей делает работу с системой проще.По-моему, это все это не имеет отношения обсуждаемому вопросу.
то, что вы считаете сложностью, и с чем вы хотите бороться и снимать, я считаю упрощением«Система 1 является A и все ее компоненты являются A». «Система 2 является A, а ее компоненты могут являться B, C, D…». «Система 2 является упрощением Системы 1». Ну, ладно: сложность можно определить по-разному. Если у вас она такая — что ж поделаешь, у нас нет точки соприкосновения.
Вопрос состоит в том, стоит ли эту разнородность транслировать в нетронутом виде в язык или попытаться представить единый способ взаимодействия с этими сущностями. Поскольку взаимодействовать (создавать, удалять, просматривать, изменять… вооще, каким-то образом воздействовать) придется, я (хотя идея мне и не принадлежит) предположил, что искомое «общее» между разнородными сущностями как раз в наличии этого взаимодействия и состоит.
Подождите, но как вы из наличия взаимодействия переходите к возможности единого способа взаимодействия?
«Система 1 является A и все ее компоненты являются A». «Система 2 является A, а ее компоненты могут являться B, C, D…». «Система 2 является упрощением Системы 1».
Я не вижу связи между этими тремя высказываниями.
Подождите, но как вы из наличия взаимодействия переходите к возможности единого способа взаимодействия?Я лично никуда не перехожу. Другие люди искали способы такого перехода. К примеру, создатели ООП выдвинули гипотезу, что метафора «сообщение» позволяет это сделать. Пока эта гипотеза подтверждается и работает.
Я не вижу связи между этими тремя высказываниями.А — «объект». B, C, D, … — функции, данные, типы… Я пытаюсь сказать: все эти вещи можно реализовать через понятие объекта и свести вторую систему к форме первой. Вы на это говорите:
с чем вы хотите бороться и снимать, я считаю упрощением
Мы считаем, что A есть A, B есть B, C есть C, и что это вообще не сложность, наоборот просто до тривиальности.Отсюда я делаю вывод, что у нас разные понятия о простоте. Я свое на данном этапе обосновать не могу, в ввиду чего продолжение дискуссии смысла сейчас не имеет. Все правильно?
К примеру, создатели ООП выдвинули гипотезу, что метафора «сообщение» позволяет это сделать. Пока эта гипотеза подтверждается и работает.
Подтверждается, что метафора "сообщение" позволяет получить единый способ взаимодействия — да. Подтверждается ли, что теперь система стала проще в восприятии?
Я пытаюсь сказать: все эти вещи можно реализовать через понятие объекта и свести вторую систему к форме первой.
Теоретически можно. Но становится ли система от этого проще?
Отсюда я делаю вывод, что у нас разные понятия о простоте.
Видимо, да.
Вы говорите: эта сложность ей присуща по природе, мы с ней ничего сделать не можем кроме как отразить в неизменном в виде в наших инструментах и дальше мучится с ней всегда.Не совсем. Мы считаем, что A есть A, B есть B, C есть C, и что это вообще не сложность, наоборот просто до тривиальности.
Сложность — это пытаться интерпретировать A, B, C как A.
И именно необходимость постоянно сталкиваться с неизбежными противоречиями умозрительной модели и реальности мы считаем мучением.
Давайте пример с пояснением, почему нет ничего общего.Да у вас с lair уже была дискуссия на тему может ли сообщение быть объектом, не хочется повторяться… Но по сути у них как раз нет ничего общего. Также как у любых данных нет ничего общего с объектами, так же как у функций нет ничего общего с объектами.
Всё это приводит к созданию довольно монструозных объектных обёрток над простыми сущностями. Все мы видели эти вырожденные объекты для представления лямбда-функции, DTO и т.д.
Вот и получается, что система в которой есть объекты, данные и
функции выглядит гораздо изящнее и реалистичнее, чем система, в которой есть только объекты.
Необходимость этого самого взаимодействия — и есть то самое общее.С чего вдруг это должно быть единое взаимодействие? Даже в физике пока его не нашли, аж целых 4 разных до сих пор :-)
Обращаясь к объекту, вы каждый раз стараетесь вспомнить/понять, изменит объект свое состояние или нет? Если так, то зачем?
Да, именно так. Затем, чтобы знать — соседние ссылки на тот же объект будут изменены благодаря этому сообщению, или нет.
Пример из реальной жизни:
mappings = GetMappingsForObject();
_mappings = mappings;
_filteredMappings = Filter(mappings);
//...
IEnumerable<string> Filter(IEnumerable<Mapping> mappings)
{
foreach (var mapping in mappings)
{
mapping.AdjustType();
if (IsBasic(mapping.Type))
yield mapping.Name;
}
}
Вот метод AdjustType
— он меняет состояние Mapping
, потому что так проще расчитывать, какие типы какие… и внезапно теперь в _mappings
состояние тоже поменялось, хотя из кода это вообще не очевидно. Мы это отлавливали несколько часов (и потом еще чинили ощутимое время).
Мы это отлавливали несколько часов (и потом еще чинили ощутимое время).Сочувствую и понимаю — самому таким приходится заниматься время от времени. И…?
…Вы хотите исключить момент поиска ошибок и отладки из программирования за счет полного отказа от состояния? Сомневаюсь, что даже в ФП это возможно. А если и возможно, то имеет массу других «побочных эффектов» (в самом широком смысле слова). Нет?
Сочувствую и понимаю — самому таким приходится заниматься время от времени. И…?
И то, что сделав изменяемость состояния униформной, мы можем обезопасить себя от таких ошибок.
Сомневаюсь, что даже в ФП это возможно.
Добро пожаловать в Erlang.
А если и возможно, то имеет массу других «побочных эффектов» (в самом широком смысле слова). Нет?
Как раз "побочные эффекты" от этого уменьшаются, простите за игру слов. Но да, потребление памяти растет, производительность может падать, типовые алгоритмы перестают быть применимыми — все так. Типичная плата за униформность.
сделав изменяемость состояния униформной, мы можем обезопасить себя от таких ошибок.Поясните, пожалуйста.
Типичная плата за униформность.Типичная — да. Обязательная — не факт. Но я не хочу уходить в очередной круг софистики, уж извините.
Поясните, пожалуйста.
Если мы знаем, что все состояние неизменно, мы можем спокойно передавать наши данные в любую обработку, не опасаясь за них. Если мы знаем, что все состояние изменяется, мы всегда будем копировать данные перед обработкой, если нам важно их состояние (заметьте, что второй вариант уже сложнее).
Типичная — да. Обязательная — не факт.
Да обязательная, обязательная. Не существует zero-cost abstractions, мир не униформен. Чем больше вы хотите охватить своей абстракцией, тем тоньше она будет растянута, тем сильнее она будет течь.
Если мы знаем, что все состояние изменяется, мы всегда будем копировать данные перед обработкой, если нам важно их состояние (заметьте, что второй вариант уже сложнее).Ну и исходите из того, что состояние может измениться — если используете неизвестный объект. Если же требуется неизменность состояния, то используйте правильные объекты и специфицируйте это требование (по возможности).
… это означает, что каждый неизвестный объект придется копировать. А если его разработчик не предоставил такой возможности — то это боль.
Это радикально повышает мои накладные расходы (как во время разработки, так и во время выполнения)
Это в лучшем случае уменьшит мои накладные расходы, но не сведет их к нулю.
Из отсутствия zero-cost abstractions я это знаю.
О нет, в моей модели вселенной выходов больше одного.
… в частности, простой выход для этой конкретной проблемы состоит в том, чтобы сказать: есть объекты, и есть значения (важно, не примитивные типы, а именно значения), внутреннее состояние объектов изменяемо, внутреннее состояние значений неизменно. Дальше я могу рассуждать — и описывать контракты — в этих двух терминах, резко снизив количество раз, когда мне надо задуматься, как я могу работать с конкретной сущностью.
Я вообще склоняюсь к тому, чтобы не упоминать про состояние (внутреннюю память объекта) в «универсальной» части определения ООП.
Я смотрю, вы в своем определении все дальше уходите от Кэевского понимания.
У меня нет опыта работы с Smalltalk, но если взять допустим C#, то концепция «всё есть объект» течёт там как дырявое решето на каждом шагу. И ничего путного Вы не напишете, если будете слепо её придерживаться.
Вы меня извините, но чем больше я с Вами общаюсь, тем сильнее ощущение, что Вы преимущественно теоретикВы считаете это оскорблением? :) Что значит «преимущественно теоретик»? Если, к примеру, человек 40 часов в неделю занимается решением практических задач, но недоволен тем, как приходится это делать и в свободное время пытается понять почему и найти решения — он «преимущественно теоретик»? :)
если взять допустим C#, то концепция «всё есть объект» течёт там как дырявое решето на каждом шагу. И ничего путного Вы не напишете, если будете слепо её придерживаться.Так ее и не удается слепо придерживаться! И даже не слепо. :)
На самом деле: если на «борьбу за чистоту» приходиться тратить слишком много усилий — оно не имеет смысла. Вы, видимо, не представляете себе, что может быть иначе… и обобщаете.
если на «борьбу за чистоту» приходиться тратить слишком много усилий — оно не имеет смысла.Именно!
Вы, видимо, не представляете себе, что может быть иначе… и обобщаете.Ну, Вы добились, что я скачал Pharo и прочитал tutorial от ProfStef xD
Дело не в распределении часов, а в том, что Вы делаете вид, что теоретической красоты достаточно, а то, что на практике это не работает, — это досадная мелочь, не достойная внимания )))Я-то как раз хочу чтобы «теоретическая красота» работала на практике. Меня не очень устраивают оба варианта: и не работающая красота, и работающая (обычно очень условно) некрасота.
Вы добились, что я скачал Pharo и прочитал tutorial от ProfStef xD
Это победа! :)
Возможности IDE впечатляют, хоть сам редактор кода слабоват, зато System Browser, Spotter, Finder (by examples), Debugger реализованы весьма интересно. Думаю, оттуда и дальше будут черпать вдохновение для инструментов разработки под другие языки.
Что касается, стандартной библиотеки, то она выглядит как ActiveSupport на стероидах — десятки тысяч методов, которые в 99.999% случаев вам не понадобятся… и, насколько я понял, без возможности подключить только нужные, потому что они все сразу в базовых классах реализованы, а не добавлены через композицию.
сам редактор кода слабоватДумаю, это следствие того, что довольно редко встречаются методы длиннее нескольких строк. Да и несколько строк — это уже запашок для Smalltalk …по крайней мере, для меня.
десятки тысяч методов, которые в 99.999% случаев вам не понадобятся… и, насколько я понял, без возможности подключить только нужные, потому что они все сразу в базовых классах реализованы, а не добавлены через композициюМожет, нам и не понадобятся. Может, нужны для работы самой системы. А даже если и нет — они чем-то сильно мешают? У меня с этим особых проблем не было, лежат себя спокойно и лежат. Бывает, их приходится пролистывать, когда ищешь нужный метод, но это редко — нужные методы редко подбираются путем просмотра всего протокола класса…
Хотя, на самом деле, считаю, средства управления кодом, безусловно надо развивать дальше…
Ну, и многие методы, разумеется, «подгружаются» в классы из пакетов. Состав базового образа Pharo, насколько я понимаю, до сих пор в стадии формирования — система-то относительно молодая (хотя и базируется на «бородатом» Squeak-е)… С другой стороны, я не уверен, что этот процесс когда либо закончится или даже должен закончиться.
Думаю, это следствие того, что довольно редко встречаются методы длиннее нескольких строк.Ну, это в принципе даже плюс. Судя по редактору, типичный метод должен уместиться в 11 строк, включая комментарии.
А даже если и нет — они чем-то сильно мешают?На самом деле спасают только продвинутые средства навигации по всему этому добру. Хотя я не уверен, что даже они позволяют всегда быстро найти то, что нужно. В общем, на мой субъективный взгляд они явно переборщили с набором методов.
Ну, и многие методы, разумеется, «подгружаются» в классы из пакетов.А можно пример? Реально интересно посмотреть.
Можно открыть Monticello Browser -> выбрать пакет -> Browse: то, что лежит в *Extensions — расширяет уже существующие классы.
Можно открыть System Browser -> выбрать класс -> в окне протоколов светло-серые начинающиеся с символа * категории — добавленные из других пакетов методы.
Реализация механизма примитивненькая, куценькая и не очень удобная; унаследована от Squeak, а там, судя по всему, было сделано по принципу The Simplest Thing That Could Possibly Work.
А в пакете они появятся автомагически сразу после Accept :-)
Я-то как раз хочу чтобы «теоретическая красота» работала на практике.Тогда Вам реально в Elixir.
Я намедни ещё на тему модели акторов читал и наткнулся на прекрасное описание Erlang… в нём очень хорошо сформулировано то, что я Вам пытался донести.
Что касается Elixir, то он добавил в Erlang ещё и метапрограммирование по образцу Lisp. И практически весь его синтаксис построен на макросах времени компиляции.
Вот, например, реализация синтаксиса для записи диапазонов: kernel.ex#L2597..L2638
Вообще, «ООП» это очень расплывчато определенное понятие, которое после многих лет странных трактовок лучше перестать употреблять.
Что такое «инкапсуляция» и «полиморфизм» по отдельности, как работает наследование в C++ или Java или .NET или Python — понятно. Особенности JS или Smalltalk тоже можно изучить. Зачем нужны паттерны проектирования в определенных языках — тоже более менее понятно. То есть, все практически-значимые вопросы не вызывают у меня вопросов.
Вот только это все почти не имеет отношения к собственно ООП. Что такое ООП в целом — мне непонятно, и, честно говоря, не очень хочется это «понимать». Есть практические реализации ООП как его поняли авторы конкретных языков, это важно понимать. А сферическое ООП в вакууме, о котором тут куча камментов, как минимум ненужно, а то и вредно.
ООП, как известно, зиждится на трёх китах: инкапсуляция, наследование, полиморфизм. Вы реализовали только первое из свойств. Наследования нет. Как нет и возможности отделить мухи от котлет, отделить классы с совпадающими методами от классов, реализующих один и тот же интерфейс. Что, вроде как, зовётся полиморфизмом. Нет, вы, конечно, можете подпихнуть pid любого модуля, но, вот незадача, у вас нет встроенного метода узнать, реализует ли он нужный интерфейс или просто имеет пару схожих методов.
Потому, пожалуйста, измените желтушный заголовок. Или добавьте наследование и полиморфизм в следующих статьях.
Наследование — создание нового класса путём дополнения существующего класса новыми свойствами без изменения наследуемого класса.
Полиморфизм — возможность обращаться к методам класса без явного знания о его конкретном типе. При этом обращение происходит по базовому классу и программист имеет гарантии, что нужный метод будет реализован именно с той сигнатурой, что и в базовом классе.
И как быть с объектно-ориентированным языками без классов — что там с наследованием?Это которые прототипно-ориентированные? Которые Луа и иже с ними?
А чем они отличаются от аспектно-ориентированных? Посмотрите внимательнее, и окажется, что практически ничем, обыкновенный public class Class extends HashMap<String,Object>, в которых Object может быть кем угодно, от char до function, и получается на лету. А АОП хорошо реализуется через ООП, являясь надстройкой. Так что никак с ними не нужно быть.
А то, что реализовано в статье, по терминологии Гради Буча называется программированием с помощью абстрактных типов данных.
Это которые прототипно-ориентированные? Которые Луа и иже с ними?Это, в первую очередь, Self. К АОП не имеет отношения. Давайте для начала разберемся с ООП без классов, а то эти самые классы у вас на каждом шагу. А потом вернемся к остальным определениям. Не против?
И чем не укладывается Self в, грубо,
class Class extends HashMap<String,Object> {
Class prototype;
Class setPrototype (Class toCopy) { this = toCopy; prototype = toCopy;}
}
Может быть я чего-то не знаю о Self? Всё-таки я его не изучал. Но JavaScript проваливается в это определение чуть ли не по шейку. А он, насколько мне известно, самый что ни на есть прототипно-ориентированный.
Не против?Против.
То, что есть иные подходы к реализации ООП не отменяет того, что в статье нет реализации ООП.
И чем не укладывается Self в, грубо, <…>Как минимум, двумя вещами:
- Механизмом создания объектов
- Механизмом связывания сообщения с методом
Но вопрос-то был в другом: язык без классов может являться объектно-ориентированным или нет?
Против.Ваше определение полностью базируется на понятии класса. (Кстати, вы не определили, что это такое.) Либо мы утверждаем, что без классов ООП не бывает (что, очевидно, не так… но, возможно, не всем очевидно; да и почему бы тогда не назвать это класс-ориентированным программированием? слово «объект» там вообще не фигурирует), либо ваши определения требуют коррекции. Во втором случае мы можем вернуться к вопросу о ООП в данной статье только после соответствующей коррекции.
То, что есть иные подходы к реализации ООП не отменяет того, что в статье нет реализации ООП.
Как минимум, двумя вещами:
1) Объекты либо клонируются с указанием предка, либо создаются с нуля. Интерфейс подобного поведения я указал. Разве чего-то не хватает?
2) Сигналы-слоты так же запросто реализуются.
Да, синтаксис будет отличаться, в силу ограничений языка, всё-таки нельзя безболезненно скрестить разные типизации. Это не такие критические вещи, на которые стоило бы обращать внимание в данном контексте.
Но вопрос-то был в другом: язык без классов может являться объектно-ориентированным или нет?А разве в self нет классов? Я вижу как минимум один — объект. Есть ещё базовые типы, которые тоже классы, но, так как концепция не позволяет, мы их так называть не будем, да?
(Кстати, вы не определили, что это такое.)Ну так погуглите. Я вам не учебник по ООП. И гуглите тщательнее, а то в школу обратно попадёте.
Либо мы утверждаем, что без классов ООП не бываетБез либо.
И какие же гарантии того, что "нужный метод реализован именно с той сигнатурой", есть в JavaScript?
Ага, то есть в JavaScript — и, по расширению, во всех динамических языках — ООП нет?
Не буду говорить за абсолютно все динамические языки
А что в них может отличаться в этом контексте?
JavaScript — точно не ООП. Не после того, как он впитал в себя ПОП, АОП и ФП.
Эээ, то есть мультипарадигменные языки не поддерживают каждую из парадигм в отдельности? Это как-то странно.
(и если ПОП — это прототипно-ориентированное программирование, то оно является частным случаем объектно-ориентированного)
Понятия не имею, потому и не говорю.
> то есть мультипарадигменные языки не поддерживают каждую из парадигм в отдельности? Это как-то странно
Вы же сами меня спросили:
> И какие же гарантии того, что «нужный метод реализован именно с той сигнатурой», есть в JavaScript?
А теперь потрудитесь объяснить, как так получается, что в ООП у программиста нет гарантированного способа узнать, что у экземпляра определённого класса реализован определённый интерфейс, то есть нужный метод реализован именно с той сигнатурой? Это и не АОП, и не ООП, это уже цирк какой-то. Сбрасывание барахла в коробки. Это был бы ООП, если бы такой класс вызывал бы ошибку на этапе компиляции\интерпритации, но не факт, что вы эту ошибку получите даже в рантайме. Даже вызвав метод с ошибочным интерфейсом.
А теперь потрудитесь объяснить, как так получается, что в ООП у программиста нет гарантированного способа узнать, что у экземпляра определённого класса реализован определённый интерфейс, то есть нужный метод реализован именно с той сигнатурой?
А должен быть (гарантированный способ)?
Это был бы ООП, если бы такой класс вызывал бы ошибку на этапе компиляции\интерпритации, но не факт, что вы эту ошибку получите даже в рантайме. Даже вызвав метод с ошибочным интерфейсом.
Хм. Давайте-ка начнем с простого вопроса: Smalltalk — ООП?
А разве не это называется классом? Ах, да, мы же отказались от классов, да… А почему мы тогда говорим об объектах? Да, объект — чёрный ящик, но это чёрный ящик с ручками, которые, даже если и прикрутили у меня на виду, всё равно не взялись из воздуха, а лежали рядом с инструкцией на икеевском. Ящик, а не коробка ручек, из которой мы в любой момент можем выкинуть что угодно, не потеряв при этом приемственности. Тогда давайте говорить о контейнерно-ориентированных языках, потому что мы реально забрасываем всё в контейнеры, и копируем контейнеры. И всё.
> Давайте-ка начнем с простого
Давайте перейдём сразу к сути: вы хотите спросить, почему я называю ООП тот язык, у которого на лету может поменяться как реализация класса, так и интерфейс?
Ровно потому, что я могу гарантировать какой-либо интерфейс через механизм наследования. Потому что я определяю интерфейс и реализую его в наследниках, и если я накосячил на одном из уровней, я сразу узнаю, что мой наследник является абстрактным классом, к примеру. Этого более, чем достаточно, чтоы говорить об ООП. Но в JavaScript, по вашим же словам, я такой возможности не имею.
Давайте перейдём сразу к сути: вы хотите спросить, почему я называю ООП тот язык, у которого на лету может поменяться как реализация класса, так и интерфейс?
Нет, почему вы называете ООП тот язык, в котором вы не знаете вплоть до момента выполнения, поддерживает ли получатель то сообщение, которое вы ему отправляете. И никакого гарантированного способа это узнать до этого момента у вас просто нет.
>> на лету может поменяться как реализация класса, так и интерфейс
Важно не это. Важно то, что я не могу создать наследника класса, в котором не будут реализованы все сообщения прототипа с нужным интерфейсом.
Важно то, что я не могу создать наследника класса, в котором не будут реализованы все сообщения прототипа с нужным интерфейсом.
… и что? Где гарантия, что в переменной, которой вы посылаете сообщение, лежит объект именно этого класса?
В целом я понял, почему практически все динамические языки являются прототипно-ориентированными. Потому что слишком дорого поддерживать классы в динамике. И почему они ООП, все типы являются наследниками типа Object: от целых до классов. И почему у меня не поднимается язык назвать их объектно-ориентированными — кроме «найти содержимое по имени поля», «клонировать другой объект» и стандартных операторов у этих классов нет никаких методов.То есть гарантируемый интерфейс дико скуп, а всё наносное — это, извините, обещания, верить которым — дело лично каждого. Лично у меня фраза «а у нас приличным гражданам принято верить на слово» не вызывает никакого доверия.
Хорошо иметь гарантию внутри своего класса.
… которых у вас тоже с гулькин нос. Кто-нибудь гарантирует, что все реализации метода возвращают объект, отвечающий на одни и те же сообщения? Кто-нибудь гарантирует, что все реализации метода требуют одинаковых сообщений от входных параметров?
В целом я понял, почему практически все динамические языки являются прототипно-ориентированными.
М? Ruby? PHP? Python?
И почему у меня не поднимается язык назвать их объектно-ориентированными
Ну то есть Smalltalk вы все-таки не называете объектно-ориентированным?
Поясняю: ООП, как известно, зиждится на трёх китах: инкапсуляция, наследование, полиморфизм.
Кому "известно"?
Нет, вы, конечно, можете подпихнуть pid любого модуля, но, вот незадача, у вас нет встроенного метода узнать, реализует ли он нужный интерфейс или просто имеет пару схожих методов.
… и это никак не связано с полиморфмизмом, а всего лишь говорит об отсутствии — в этом месте — строгой типизации. Утиный полиморфизм — прекрасная вещь.
… и это никак не связано с полиморфмизмом, а всего лишь говорит об отсутствии — в этом месте — строгой типизации. Утиный полиморфизм — прекрасная вещь.
Я бы согласился, если бы было наследование. Или если бы был способ узнать, реализован ли нужный интерфейс или нет ДО вызова нужного метода.
всем, изучавшим ООП
Ну, я вот изучал ООП. Автор поста, насколько я понимаю, тоже. chaetal — тоже. Но внезапно, всем троим это не известно.
почитайте любую книжку по ООП.
Если завтра выйдет еще одна книжка по ООП, в которой будет сказано, что ООП зиждется на насилии, прелюбодеянии и гордыне — будем ей верить?
А если серьезно, то есть разные книжки по ООП. Скажем, в Баддсовской "An Introduction To Object Oriented Programming", определение другое. И вообще их много. Даже вики приводит другое определение:
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which may contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods.
Я бы согласился, если бы было наследование.
Наследование для полиморфизма не обязательно.
Или если бы был способ узнать, реализован ли нужный интерфейс или нет ДО вызова нужного метода.
То есть, стоит добавить операцию School.isSchool(pid)
, как немедленно появляется полиморфизм?
Ну, я вот изучал ООП.Видимо по книге, в которой
сказано, что ООП зиждется на насилии, прелюбодеянии и гордынеПочитайте стандарты SIMULA и Smalltalk, основоположников ООП. Там, внезапно, есть и наследование, и полиморфизм, и инкапсуляция.
А если серьезно, то есть разные книжки по ООП. Скажем, в Баддсовской «An Introduction To Object Oriented Programming», определение другое. И вообще их много.Если мы будем брать определения ООП по книжкам для новичков, мы никогда ни к чему не прийдём. Давайте основываться на каких-нибудь обязующих текстах, на стандартах к языкам, к примеру.
Наследование для полиморфизма не обязательно.Для полиморфизма необходимо, как минимум, определить интерфейс взаимодействия. После этого класс должен обязаться его реализовать. Что это, если не наследование?
То есть, стоит добавить операцию School.isSchool(pid), как немедленно появляется полиморфизм?Нет, так как у вас нет способа узнать, чей это метод. Нужно тогда, как минимум, хранить имена всех базовых классов, их методы и параметры, и при каждом вызове искать в таблице, есть у нас такой метод или нет.
Почитайте стандарты SIMULA и Smalltalk, основоположников ООП. Там, внезапно, есть и наследование, и полиморфизм, и инкапсуляция.
(а) пожалуйста, конкретную ссылку
(б) почему вы считаете, что эти стандарты — определяющие в ООП, а другие определения нерелевантны?
Если мы будем брать определения ООП по книжкам для новичков, мы никогда ни к чему не прийдём.
Ага, то есть не все книжки равны?
Для полиморфизма необходимо, как минимум, определить интерфейс взаимодействия
Это определение обязано быть в коде? Нет.
После этого класс должен обязаться его реализовать.
Это зачем еще? Может и классов не быть же.
Что это, если не наследование?
Внезапно, это реализация интерфейса. Которая не обязана быть наследованием.
Нет, так как у вас нет способа узнать, чей это метод.
Какой именно метод и что значит "чей"?
Нужно тогда, как минимум, хранить имена всех базовых классов, их методы и параметры, и при каждом вызове искать в таблице, есть у нас такой метод или нет.
Извините, но выше вы писали "нужен способ узнать, реализован ли нужный интерфейс". Почему School.isSchool(pid)
не удовлетворяет этой фразе?
Сами найдёте. Не великий труд, всё на вики есть.
> почему вы считаете, что эти стандарты — определяющие в ООП, а другие определения нерелевантны?
Давайте котлеты отдельно, а мух — отдельно. Если у нас новые стандарты определяют что-то другое, то давайте этому новому новое, извините за тавтологию, имя. Если у вас есть ООП, а потом появляется ПОП, который может быть реализован через ООП, но, в целом, не обязательно удовлетворяет условиям ООП, так, быть может, не называть его частным случаем ООП? Если у вас ООП, в котором нет наследования, да и, в общем, полиморфизма, так значит, это не ООП, а, скажем, структурное программирование?
Так уж сложилось, что Симула появилась задолго до всех прочих концепций. Право первого. И, если заложенные на тот момент концепции отличаюся от современного понимания ООП, и вы против изменения привычного вам мира, доназовите старые концепции, введите их в общепринятый лексикон и наслаждайтесь идилией.
> Ага, то есть не все книжки равны?
Вы разве не знали? Конечно не равны. То, что можно Зевсу, нельзя быку. То, что можно Аллаху, нельзя Гитлеру. За первую книжку можно засудить человека, а за вторую — сесть в тюрьму, хотя в обеех есть пикантные формулировки.
Конкретно в вашем примере некорректно сравнивать научно-популярную книжку, в которой всё объясняется на пальцах, и строгий инжинерный\научный язык. Ферштейн?
> Это определение обязано быть в коде? Нет.
Да. Или для вас внешний xml — это уже не код, а задний проход?
> Это зачем еще? Может и классов не быть же.
Ну так и ООП может не быть. В чём проблема-то?
> Внезапно, это реализация интерфейса. Которая не обязана быть наследованием.
Не обязана, конечно. Если вы пропускаете пункт про обязательство его реализовать. А обязательство — это не пустой звук, это, опять же, некоторый унифицированный способ узнать, что ваше творение полностью реализует указанный интерфейс. И, если такой способ есть, ваша реализация становится классом, вне зависимости от вашего желания. Потому что именно и только класс даёт гарантию реализации интерфейса (Если мы вводим полноценное наследование, уместно слово «частичной и полной», в противном случае только «полной»).
> Извините, но выше вы писали «нужен способ узнать, реализован ли нужный интерфейс». Почему School.isSchool(pid) не удовлетворяет этой фразе?
А как узнать, реализован ли метод «isSchool»? А как узнать, что это метод «School.isSchool(pid)», а не «Building.isSchool(pid, floors[])? А как узнать, что это „Human.lead(pid, to)“, а не „AlchemistryGenerator.lead(pid, to)“?
Если у вас есть ООП, а потом появляется ПОП, который может быть реализован через ООП,
Вообще-то, наоборот. ООП может быть реализовано через ПОП.
Так уж сложилось, что Симула появилась задолго до всех прочих концепций. Право первого.
Вот только если мы говорим о "праве первого", то (утверждается, что) термин ООП придумал Алан Кэй, и его определениие звучит так: "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things". Впрочем, и в первом его определении наследования нет (хотя вот классы есть).
Вы разве не знали? Конечно не равны.
Тогда нельзя взять "любую" книжку про ООП, можно брать только конкретную. А дальше возникает вопрос, кто определяет, какая книжка про ООП равнее других...
Да. Или для вас внешний xml — это уже не код, а задний проход?
При чем тут внешний xml? Интерфейс (точнее — контракт) может быть определен и просто на словах. Он перестал быть контрактом от этого?
А обязательство — это не пустой звук, это, опять же, некоторый унифицированный способ узнать, что ваше творение полностью реализует указанный интерфейс.
… и где же в определении ООП есть требование такого унифицированного способа?
Потому что именно и только класс даёт гарантию реализации интерфейса
Оу, вы правда не слышали про анонимные реализации?
А как узнать, реализован ли метод «isSchool»?
Внезапно, в Elixir есть типизация, которая вам об этом скажет.
Есть иное мнение.
http://c2.com/cgi/wiki?AlanKaysDefinitionOfObjectOriented
Кроме того, я не настаивал на том, что именно его определение должно быть определяющим. Во многом, потому, что оно слишком короткое и общее, чтобы стать таковым, под него можно много чего подогнать. Я лишь указывал, что если у нас один термин означает слишком много, нужно добавить терминов. Или использовать формулировки из стандартов, в которых такие моменты будут хорошо прописаны.
> Вообще-то, наоборот. ООП может быть реализовано через ПОП.
Интересно, как вы введёте классы?
> Тогда нельзя взять «любую» книжку про ООП, можно брать только конкретную.
Давайте не впадать в оурэлевщину.
> При чем тут внешний xml? Интерфейс (точнее — контракт) может быть определен и просто на словах. Он перестал быть контрактом от этого?
Проверка выполнения обязательств.
> … и где же в определении ООП есть требование такого унифицированного способа?
Класс.
> Оу, вы правда не слышали про анонимные реализации?
Анонимные реализации чего? Класса? То, что вы не дали ему имя, не отнимает у него звания класса. Вы просто делегировали право определить его имя дальше в недра среды исполнения.
>Внезапно, в Elixir есть типизация, которая вам об этом скажет.
O RLY?
Есть иное мнение.
Есть. Но тогда у вас вообще нет единого мнения об определении ООП.
Или использовать формулировки из стандартов, в которых такие моменты будут хорошо прописаны.
А кто сказал, что в стандарте правильно сделано? Вы ошибочных стандартов не видели?
Интересно, как вы введёте классы?
Никак. Классы не обязательны для ООП.
Проверка выполнения обязательств.
Контракт обязательно машинно-верифицируем?
Класс
В определении ООП его нет.
Анонимные реализации чего?
Интерфейса.
O RLY?
Да.
Видимо, так оно и есть.
> А кто сказал, что в стандарте правильно сделано? Вы ошибочных стандартов не видели?
А кто сказал, что Алан Кэй, будучи уже в преклонном возрасте, не забыл (или не передумал) того, что он сказал ранее? Иначе истолкавал свои старые высказывания?
> Никак. Классы не обязательны для ООП.
Если мы рассматриваем только ПОП подмножество. Меня же ниже уже ткнули носом в незнание терминов, и что ООП — более общее, нежели КОП, так что, расскажите пожалуйста, как вы реализуете КОП через ПОП, как вы сделаете классы?
> Контракт обязательно машинно-верифицируем?
Вы программист или лавочник?
> Интерфейса
А разве интерфейс — это не из КОП? Разве интерфейс — это не частный случай класса, имеющий только виртуальные методы без реализации?
> Внезапно, в Elixir есть типизация, которая вам об этом скажет.
Она мне скажет что? Что она мне скажет на „Human.lead(pid, to)“ vs „AlchemistryGenerator.lead(pid, to)“? человек.веди против генератор.свинец?
Ooo! Так, вам обратно в школу?!?Но тогда у вас вообще нет единого мнения об определении ООП.Видимо, так оно и есть.
Видимо, так оно и есть.
Тогда ваше утверждение "ООП, как известно, зиждится на трёх китах: инкапсуляция, наследование, полиморфизм" — неверно. О чем с начала и говорили.
более общее, нежели КОП, так что, расскажите пожалуйста, как вы реализуете КОП через ПОП, как вы сделаете классы?
Эээ, и снова, если ПОП — это один из способов реализации ООП (и, следовательно, можно реализовать ООП через ПОП), а КОП — это другой способ реализации ООП (и, следовательно, ООП можно реализовать через КОП), то почему внезапно КОП должно быть можно реализовать через ПОП?
Вы программист или лавочник?
Я программист, и я в своей жизни видел дофига неверифицируемых (машинно) контрактов.
А разве интерфейс — это не из КОП?
Нет, это более общее понятие.
Разве интерфейс — это не частный случай класса, имеющий только виртуальные методы без реализации?
Тоже нет. Точнее, в рамках определенных контекстов это так, но в рамках программирования в целом — не так.
Что она мне скажет на „Human.lead(pid, to)“ vs „AlchemistryGenerator.lead(pid, to)“? человек.веди против генератор.свинец?
Зависит от типа to
. Если они одинаковые, то ничего (потому что типизации по pid
нет).
Или оно верно, но заумные формулировки заумных людей размыли эти понятия, так что наблюдателю извне сразу и не понятно, где находится каждый из китов. Где находятся они в JavaScript я вам уже ответил выше. Про АОП, думаю, нет смысла писать, там всё достаточно очевидно.
Почему я так привязался к интерфейсам? Потому, что интерфейс — единственное, что отделяет объекты от структур. Если у объекта нет интерфейса, то он не объект, а набор данных. И если интерфейс неизвестен — это тоже не объект, потому как мы с ним не можем взаимодействовать, даже создать. (Под «интерфейс неизвестен» я имею в виду не положительный\отрицательный ответ на попытку вызвать метод, а отсутствие возможности вызвать этот метод, т.е., к примеру, полностью приватные классы С++)
> Эээ, и снова, если ПОП — это один из способов реализации ООП (и, следовательно, можно реализовать ООП через ПОП), а КОП — это другой способ реализации ООП (и, следовательно, ООП можно реализовать через КОП), то почему внезапно КОП должно быть можно реализовать через ПОП?
Сойдёмся на том, что ПОП — подмножество КОП и пойдём дальше.
> Нет, это более общее понятие.
Разве интерфейс — это не набор обязательств объекта, то есть тех методов, без которых объект не может существовать? Если нет, то смысл говорить об интерфейсе как о вещи, которая «может быть, а может и не быть, а может быть, но не вся и вообще я тебе завтра отвечу»? Если да, то чем отличаются интерфейс и абстрактный класс?
> Точнее, в рамках определенных контекстов это так, но в рамках программирования в целом — не так.
В рамках ООП естественно, Prolog мы не трогаем.
> Зависит от типа to. Если они одинаковые, то ничего (потому что типизации по pid нет).
И где, собственно, тогда интерфейс? И что, собственно, тогда интерфейс?
Или оно верно
На основании чего оно верно?
Потому, что интерфейс — единственное, что отделяет объекты от структур.
Это зависит от того, в каком языке вы находитесь. Скажем, в C# структуры — тоже объекты (и тоже имеют поведение).
Сойдёмся на том, что ПОП — подмножество КОП и пойдём дальше.
Это утверждение неверно. В JS нет классов.
Разве интерфейс — это не набор обязательств объекта, то есть тех методов, без которых объект не может существовать?
Действительно, интерфейс (точнее, повторюсь, контракт) — это набор тех операций, которые объект позволяет над собой совершать.
Если да, то чем отличаются интерфейс и абстрактный класс?
Тем, что набор операций не обязательно реализовывать в виде класса.
И где, собственно, тогда интерфейс? И что, собственно, тогда интерфейс?
В данном случае интерфейс — это набор методов, выставленных модулем School
, Human
или AlchemistryGenerator
.
Ну же, я вам уже 2 раза написал. На основании того, что объектов не может быть без интерфейсов, а интерфейсы влекут за собой классы, наследование и полиморфизм.
Классы — связка интерфейса, данных и реализации.
Полиморфизм следует из требования единообразно обращаться к объектам хотя бы при их создании (конструкторов может быть бесконечно много, но то же соглашение об имени конструктора и вызове его по оператору new, к примеру — самый базовый интерфейс).
Наследование вытекает из того, что класс, реализовавший интерфейс, не эквивалентен интерфейсу и не является исключительной реализацией.
Как бы вы не крутились, ни один ООП не обойдёт этих простых правил, а всё остальное — сахар.
> Это утверждение неверно. В JS нет классов.
Перечитайте https://habrahabr.ru/post/307720/#comment_9756054
Если в JavaScript нет классов, то что такое базовые типы и тип class? Как они могут так просто занимать общее пространство и быть взаимозаменяемы в коде, если у них нет общего предка, дающего им общий базовый интерфейс?
> Это зависит от того, в каком языке вы находитесь. Скажем, в C# структуры — тоже объекты (и тоже имеют поведение).
В C# всё классы. Или вас сбило то, что класс называется struct? Shame on you! Вы сами написали, что структура имеет поведение. Позвольте вам напомнить, что единственное отличие структуры от класса — наличие поведения, то есть методов. Впредь, пожалуйста, не лажайтесь так грубо.
> Действительно, интерфейс (точнее, повторюсь, контракт) — это набор тех операций, которые объект позволяет над собой совершать.
> Тем, что набор операций не обязательно реализовывать в виде класса.
Какая разница, где и как они записаны, если они допустимы только в контексте объекта и обязаны быть реализованы для существования объекта?
Ну же, я вам уже 2 раза написал. На основании того, что объектов не может быть без интерфейсов, а интерфейсы влекут за собой классы, наследование и полиморфизм.
Во-первых, не объекта не может быть без интерфейса, а у любого объекта обязательно будет интерфейс. В смысле — создадите объект, появится и интерфейс. Более того, можно не иметь объекта, но иметь интерфейс.
А во-вторых, для реализации интерфейса (даже если это отдельная языковая сущность) наследование не нужно.
Если в JavaScript нет классов, то что такое базовые типы и тип class?
Базовые типы — это базовые типы. А что за тип class
— я не знаю, я его не видел никогда в JavaScript.
В C# всё классы.
А вот это — неправда. В C# есть классы и есть структуры.
Позвольте вам напомнить, что единственное отличие структуры от класса — наличие поведения, то есть методов.
Это, повторюсь, зависит от языка, в котором вы находитесь. В C# это не так.
Какая разница, где и как они записаны, если они допустимы только в контексте объекта и обязаны быть реализованы для существования объекта?
Принципиальная: если мы допускаем, что операции существуют в объекте, то нам не нужны классы.
Как вы создадите объект, не имеющий интерфейса создания?
> А во-вторых, для реализации интерфейса (даже если это отдельная языковая сущность) наследование не нужно.
Есть маленькая тонкость, вы исходите из своего толкования термина «интерфейс» на основе ПОП программирования. Но в таком случае вы упускаете одну проблему, объект не проверяем на соответствие интерфейсу. Вы хотите сказать, что это ОК, но, извините, это не так. Как всё может быть в порядке, если сам объект не в курсе своего интерфейса? В таком случае функция — это не метод, а данные. А методом будет операция «попытаться выполнить поле как функцию над следующими параметрами» — оператор круглых скобок.
> А вот это — неправда. В C# есть классы и есть структуры.
> Это, повторюсь, зависит от языка, в котором вы находитесь. В C# это не так.
https://msdn.microsoft.com/ru-ru/library/saxz13w4.aspx
>> Все структуры наследуют непосредственно от System.ValueType, который наследует от System.Object. <<
Ой!
> Базовые типы — это базовые типы. А что за тип class — я не знаю, я его не видел никогда в JavaScript.
Я — не я, базовые типы — не классы, даже если они себя ведут, как классы! Что это тогда, если не классы?
https://learn.javascript.ru/es-class
Как вы создадите объект, не имеющий интерфейса создания?
Через фабрику. Но если вы обратите внимание, я говорю, что у любого объекта есть интерфейс, так что это бессмысленный вопрос.
Есть маленькая тонкость, вы исходите из своего толкования термина «интерфейс» на основе ПОП программирования.
Нет, с чего вы это взяли?
Как всё может быть в порядке, если сам объект не в курсе своего интерфейса?
Объект как раз прекрасно в курсе.
Ой!
Не "ой". "Наследует от System.Object" не означает "является классом".
Я — не я, базовые типы — не классы, даже если они себя ведут, как классы! Что это тогда, если не классы?
В каком смысле "ведут себя как классы"? Может, это классы ведут себя как типы (которыми они, кстати, и являются)?
https://learn.javascript.ru/es-class
Фразу "Современные возможности ES-2015" видите? В EcmaScript ключевое слово class
есть (хотя, впрочем, это все равно только синтаксический сахар над прототипами), а в JavaScript — нет.
Но, по вашим словам, сначала создаётся объект, а потом он получает интерфейс, то есть изначально его, интерфейса, нет. Как создаётся объект без интерфейса?
>Нет, с чего вы это взяли?
>>В смысле — создадите объект, появится и интерфейс.
> Объект как раз прекрасно в курсе.
>> И какие же гарантии того, что «нужный метод реализован именно с той сигнатурой», есть в JavaScript?
> Не «ой». «Наследует от System.Object» не означает «является классом».
https://msdn.microsoft.com/ru-ru/library/system.object(v=vs.110).aspx
>> public class Object <<
Ой-ой! Наследование = «является».
> Фразу «Современные возможности ES-2015» видите? В EcmaScript ключевое слово class есть (хотя, впрочем, это все равно только синтаксический сахар над прототипами), а в JavaScript — нет.
Принимается.
Но, по вашим словам, сначала создаётся объект, а потом он получает интерфейс, то есть изначально его, интерфейса, нет.
Нет. Объект всегда обладает интерфейсом (в значении "набором операций").
Как создаётся объект без интерфейса?
Никак.
Нет, с чего вы это взяли?
В смысле — создадите объект, появится и интерфейс.
ПОП тут ни при чем.
Объект как раз прекрасно в курсе.
И какие же гарантии того, что «нужный метод реализован именно с той сигнатурой», есть в JavaScript?
Не вижу связи между этими двумя фразами.
Ой-ой! Наследование = «является».
Да, любая структура в C# является System.Object
, но при этом не является классом. Вот такая вот фигня.
(На самом деле, это потому, что System.Object
— не вполне настоящий класс, а отношение между ним и System.ValueType
— не вполне наследование. Такая вот протекшая абстракция.)
Отлично. Тогда, получается, минимальный интерфейс — набор соглашений о способах создания и удаления объекта — есть всегда и присущ всем объектам. Тогда все объекты принадлежат классу создаваемых объектов.
Тогда, получается, и Self, и JavaScript имеют, как минимум, создаваемые классы, просто они находятся на нижнем уровне. Соглашения высшего же уровня, прототипы, — попытка исправить то, что классов в смысле гарантии соблюдения интерфейсов на высшем уровне нет, а без таких гарантий более-менее серьёзная раработка вообще не возможна.
> Не вижу связи между этими двумя фразами.
Ну как же. Вы же меня спросили в начале, как в JavaScript объект может проверить, есть ли у него нужный метод с нужной сигнатурой (с намёком — никак). А теперь вы же утверждаете, что способ есть, как есть и некое высшее знание. Я ничего не пропустил?
> Да, любая структура в C# является System.Object, но при этом не является классом. Вот такая вот фигня.
Я — не я, классы — не классы.
Код
public struct CoOrds
{
public int x, y;
public CoOrds(int p1, int p2)
{
x = p1;
y = p2;
} }
Stack(Object)stack = new Stack(Object)();
stack.Push(new integer(5));
stack.Push(new string(«lolwat?»);
stack.Push(Coord(5,5));
string str = stack.Last().toString();
валиден? Если да, то как Object может не быть классом? А как struct может не быть классом, если он наследуется от класса и имеет методы? Ах, они указаны как final, и operator= выполняет копирование, а не получение ссылки? И? И ничего. Структуры — это классы, потому, что то, на чём они основаны — тоже классы, то, что реализуют — классы, и то, что получается в итоге — классы.
Тогда, получается, минимальный интерфейс — набор соглашений о способах создания и удаления объекта — есть всегда и присущ всем объектам.
Нет. Интерфейс всегда рассматривается с точки зрения пользователя. И если у пользователя нет способа создать объект — например, это синглтон, или объект всегда получается из другого объекта — то у этого объекта нет интерфейса создания. Аналогично и для интерфейса удаления (которого, скажем, в C# по умолчанию у объектов нет).
Тогда все объекты принадлежат классу создаваемых объектов.
Только в том значении слова "класс", которое "категория объектов, объединенных признаком". Но, как мы помним, в class-based OOP под словом "класс" понимается другое, поэтому — в терминах class-based OOP — объекты не принадлежат к этому классу (если он вообще есть). Они реализуют этот интерфейс — если реализуют, конечно.
а без таких гарантий более-менее серьёзная раработка вообще не возможна.
То есть на динамических языках (включая Smalltalk) серьезная разработка невозможна?
А теперь вы же утверждаете, что способ есть, как есть и некое высшее знание. Я ничего не пропустил?
Вы путаете гарантию реализации и способ проверить, есть ли метод. Одно в JS есть (по крайней мере, на уровне имени метода), другого — нет.
Если да, то как Object может не быть классом?
Легко.
А как struct может не быть классом, если он наследуется от класса и имеет методы?
Аналогично, легко.
Структуры — это классы, потому, что то, на чём они основаны — тоже классы, то, что реализуют — классы, и то, что получается в итоге — классы.
Не в C#. В C# очень конкретная терминология, в которой struct
и class
имеют разную семантику, и, что важно, разное поведение.
В частности, это очень хорошо видно на следующем коде:
CoOrds locker = new CoOrds(1, 2);
lock(locker) //не будет работать, точнее, всегда будет входить внутрь лока
{
}
Ваши логические пассы верны для КОП, в котором базовый интерфейс поддаётся значительному изменению, но в ПОП это не так. Нет у javascript private, а, значит, и способов сокрыть базовый интерфейс.
Да и в КОП это не вполне корректно, класс знает о стандартном интерфейсе, и, чтобы его избежать, нужно явно прописывать подавляемые соглашения. Для пользователя чёрного ящика, конечно, нет способа самостоятельно получить такой, но интерфейс взаимодействия не исчезает.
Ваши логические пассы верны для КОП, в котором базовый интерфейс поддаётся значительному изменению
Значит, они верны хотя бы для части ООП. Этого достаточно.
в ПОП это не так. Нет у javascript private, а, значит, и способов сокрыть базовый интерфейс.
В JavaScript — нет, а вот в других языках может и быть. Почему вы так смело говорите за всю (суб-)парадигму?
Да и в КОП это не вполне корректно, класс знает о стандартном интерфейсе, и, чтобы его избежать, нужно явно прописывать подавляемые соглашения [...] интерфейс взаимодействия не исчезает.
Исчезает. private-операция (например, private-конструктор) не являются частью интерфейса.
А главное, это все никак не меняет того факта, что для наличия интерфейса не нужно наследование (а интерфейс, на самом деле, не является определяющей частью ООП).
Нет, не достаточно. Они верны для той части ООП, в которой программист может определять интерфейсы и классы. В той части, где он не может определять классы, действуют https://habrahabr.ru/post/307720/#comment_9756778
То есть классы никуда не деваются. Девается ваш контроль над ними.
> В JavaScript — нет, а вот в других языках может и быть. Почему вы так смело говорите за всю (суб-)парадигму?
Весь интерфейс классов ПОП — создание, клонирование и доступ к полям. Я, конечно, могу представить, что подобные запреты могут вводиться, но не вижу способа сделать это элегантно и полезно. Хотя, правда ваша.
Хотя в таком случае, случае запрещения, получится другой базовый интерфейс и, соответственно, другой класс. И будет уже не совсем ПОП.
> А главное, это все никак не меняет того факта, что для наличия интерфейса не нужно наследование (а интерфейс, на самом деле, не является определяющей частью ООП).
https://habrahabr.ru/post/307720/#comment_9756092
Интерфейс — набор методов — всё, что отделяет объект от структуры. Извините, но без интерфейсов получится СОП, или просто классический процедурный яп.
Они верны для той части ООП, в которой программист может определять интерфейсы и классы.
Программист может определять интерфейс в любой части ООП. Иногда его контроль ограничен, но он всегда есть.
То есть классы никуда не деваются
Не во всяком ОО-языке есть классы.
Девается ваш контроль над ними.
Для меня это эквивалентно "классов нет".
Весь интерфейс классов ПОП — создание, клонирование и доступ к полям.
В ПОП — по крайней мере, в подавляющем количестве реализаций — классов нет.
Интерфейс — набор методов — всё, что отделяет объект от структуры.
У структуры тоже есть интерфейс — это набор предоставляемых ей данных. И у модуля есть интерфейс. И у функции есть интерфейс. У всего в программировании есть интерфейс.
(повторюсь, "интерфейс" в значении "набор предоставляемого поведения")
> Не во всяком ОО-языке есть классы.
> В ПОП — по крайней мере, в подавляющем количестве реализаций — классов нет.
В общем, нет, вы не правы, и я уже показал это на примере JavaScript.
> «интерфейс» в значении «набор предоставляемого поведения»
Разве это определение интерфейса из ООП?
In object-oriented languages, the term interface is often used to define an abstract type that contains no data or code, but defines behaviors as method signatures. A class having code and data for all the methods corresponding to that interface is said to implement that interface. Furthermore, a class can implement multiple interfaces, and hence can be of different types at the same time.
В общем, нет, вы не правы, и я уже показал это на примере JavaScript.
… в котором, как мы выяснили, классов нет. И обратного вы не показали.
Разве это определение интерфейса из ООП?
В ООП нет определения интерфейса.
In object-oriented languages, the term interface is often used to define
Есть фундаментальная разница между often used и определением.
Что же?
> То есть на динамических языках (включая Smalltalk) серьезная разработка невозможна?
Не смешивайте котлеты и мух и не перевирайте мои слова. Я даже про javascript написал, что там есть надстройка в виде прототипов, которая позволяет иметь некие гарантии. Естественно, они есть много ещё где. Насколько они эффективны — вопрос не ко мне.
> Не в C#. В C# очень конкретная терминология, в которой struct и class имеют разную семантику, и, что важно, разное поведение.
Нельзя подгонять результаты под желаемый ответ. Я прекрасно понимаю, чем отличаются структуры от классов в контексте шарпа, но не в контексте ООП. С точки зрения ООП у них общий интерфейс и общий базовый класс. Который является классом, и не перестанет им быть, даже если вы очень сильно постараетесь доказать обратное. С точки зрения пользователя, их обоих можно понизить до Object и жонглировать ими. Так в чём, с точки зрения ООП, у них разница, кроме вашей хотелки?
Что же?
"In object-oriented programming, a class is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods)"
Я даже про javascript написал, что там есть надстройка в виде прототипов, которая позволяет иметь некие гарантии
Какие же?
Так в чём, с точки зрения ООП, у них разница, кроме вашей хотелки?
С точки зрения ООП понятия "структура" не существует, поэтому этот вопрос бессмысленен.
И да, я изначально говорил, что мое разделение структур и классов языко-специфично.
https://en.wikipedia.org/wiki/Class_(computer_programming)#The_concept_of_class_interface
> Какие же?
А ви-таки почему интересуетесь?
> С точки зрения ООП понятия «структура» не существует, поэтому этот вопрос бессмысленен.
С точки зрения ООП у нас есть класс с названием «class» и есть класс с названием «struct». В чём, с точки зрения ООП, а не вашего взгляда на мир, у них отличия?
> И да, я изначально говорил, что мое разделение структур и классов языко-специфично.
Я и заметил, что у вас полифония мнений.
А ви-таки почему интересуетесь?
Потому что вы говорите, что гарантии обязательны для разработки крупных систем (изначально вы вообще, если я не ошибаюсь, говорили, что ООП без гарантий не существует).
С точки зрения ООП у нас есть класс с названием «class» и есть класс с названием «struct». В чём, с точки зрения ООП, а не вашего взгляда на мир, у них отличия?
Если мы говорим о class-based OOP, то ни в чем: и то, и другое — класс.
И то, и другое верно. Интерфейсы — это гарантии, обязательства классов. Они максимальны в статических КОП и минимальны в динамических ПОП (на уровне наследования базовых типов, пустого класса и функторов от одной базы и, как следствие, единое поведение в контейнере).
> Если мы говорим о class-based OOP, то ни в чем: и то, и другое — класс.
Мы говорим про ООП в общем, что это меняет?
Интерфейсы — это гарантии, обязательства классов.
… так какие гарантии предоставляет JavaScript?
Мы говорим про ООП в общем, что это меняет?
Это меняет то, что в "ООП в общем" не может быть "класса с названием", потому что "ООП в общем" не оперирует понятием класса. В нем может быть объект с поведением.
Ruby: x = 2. создал.
Сами найдёте. Не великий труд, всё на вики есть.Раз уж Вы сослались на вики, то откройте страничку Programming paradigm и увидьте, что модель акторов — это такая же реализации ООП, как и Class-based и Prototype-based:
К тому же в статье обсуждается ООП в контексте изначального смысла. Цитаты от автора этого термина на тему каким должен быть язык, реализующий ООП, можно найти под спойлером в начале статьи. Как видите, ни о каких «китах» там нет ни слова. Разве что про инкапсуляцию и то, только в плане сокрытия данных.
Если говорить на уровне "серьёзных книг", то после 1994 года, когда вышла "Design patterns" от GoF, реализовывать полиморфизм через наследование, а не через композицию — дурной тон.
Да и впринципе, посмотрите на современные реализации полиморфизма — сперва была мода duck typing во всяких там динамических языках, теперь — полная реализация интерфейса как к примеру в Rust.
Полиморфизм есть — наследования нету. Так что что-то у вас не сходится.
Если понимать наследование как обязательство объекта реализовывать какой-либо интерфейс — вы не правы. В конце концов, полиморфизм без наследования интерфейсов не возможен. Те же плюсы столкнулись с тем, что без нормальных проверок выполнения гарантий в шаблонах в итоге получался один нецензурный ком. А что такое проверка гарантий, как не требование соблюдать интерфейс, то есть «быть» экземпляром базового класса?
В конце концов, полиморфизм без наследования интерфейсов не возможен
Правда?
void Do(dynamic target)
{
target.Do();
}
DoSomething(new Action());
DoSomething(new Command());
DoSomething(new Piano());
Или вот:
let inline add(value1 : ^T when ^T : (static member (+) : ^T * ^T -> ^T), value2: ^T) =
value1 + value2
add 9 13
add 7.0 4.0
add "abc" "def"
Я наложил ограничения, да. Но это не интерфейс (в значении "интерфейс как сущность языка"), и нет никакого наследования.
Более того, в первом случае даже и ограничения нет никакого: если объект не будет иметь такого метода, код скомпилируется — просто в рантайме будет ошибка.
Хотя, во втором явно накладывается ограничение на тип Т на реализацию оператора сложение. Причём ранг полиморфизма чуть выше первого, так что, формально, нельзя сказать, что класс наследует интерфейс, скорее, для классов, не имеющих интерфейса с оператором сложения не создаётся метода add. Иначе говоря, такой шаблон автоматически унаследует все подходящие классы каждый от своего анонимного интерфейса.
А первый случай — немного слегка почти не ООП, потому что когда в плюсах что-то делают через (void*), это считается дурным тоном, а для адептов чистого ООП — вообще причина для ритуального сожжения. А в чём, извините, разница, кроме того, что плюсы вызывают метод не по имени, а по адресу?
Иначе говоря, такой шаблон автоматически унаследует все подходящие классы каждый от своего анонимного интерфейса.
А ничего, что "подходящие классы" — они из системной библиотеки, и модификации не подлежат?
Слишком много сущностей (анонимный интерфейс, автоматическое наследование) там, где можно обойтись ограничением на статический метод. Кстати, в некоторых (как минимум) языках статические методы не входят в иерархию реализации (не определяются в интерфейсе и не переопределяются).
А первый случай — немного слегка почти не ООП, потому что когда в плюсах что-то делают через (void*), это считается дурным тоном. А в чём, извините, разница, кроме того, что плюсы вызывают метод не по имени, а по адресу?
Ну то есть Smalltalk все-таки не ООП?
А ничего, что вы создали не метод, а функцию в функциональном языке? Ничего? Тогда движемся дальше.
> Ну то есть Smalltalk все-таки не ООП?
Если это его синтаксис и если это нормальный способ вызова своих методов, а не отсылки сообщений потенциально неизвестному классу, то по конкретно этому критерию — да. Но мне что-то подсказывает, что это не smalltalk, а java. Не знаю, то ли дело в том, что синтаксис не подходит. то ли дело в том, что в smalltack нельзя не знать собственных сообщений, то ли в жирной лыбе на вашем лице.
А ничего, что вы создали не метод, а функцию в функциональном языке? Ничего? Тогда движемся дальше.
Она (функция) от этого перестала быть полиморфной?
Если это его синтаксис и если это нормальный способ вызова своих методов, а не отсылки сообщений потенциально неизвестному классу, то по конкретно этому критерию — да.
О нет, в Smalltalk все еще веселее.
doSomething: target
target do
Action new doSomething
Command new doSomething
Piano new doSomething
Но мне что-то подсказывает, что это не smalltalk, а java.
Неправильно подсказывает.
в smalltack нельзя не знать собственных сообщений
А при чем тут собственные сообщения, когда речь идет о сообщениях, поддерживаемых параметром target
, который снаружи передается?
Она стала объектно-ориентированной?
> А при чем тут собственные сообщения, когда речь идет о сообщениях, поддерживаемых параметром target, который снаружи передается?
При том, что в smalltack есть respondsTo: doSomething
Она стала объектно-ориентированной?
Нет. Но речь шла о полиморфизме, а не об ООП.
При том, что в smalltack есть respondsTo: doSomething
… и что? Такие вещи во многих динамических языках есть. Да и в статических тоже, прямо скажем.
(BTW, если я не ошибаюсь, respondsTo
может обмануть)
Батенька, вам к лицу ваш ник. Мы уже два дня пишем про ООП, и тут, БАХ!, внезапно не про ООП, а так, о природе, о кошечках, так получается? Это не серьёзно.
>… и что? Такие вещи во многих динамических языках есть. Да и в статических тоже, прямо скажем. (BTW, если я не ошибаюсь, respondsTo может обмануть)
И-и-и-и? Кого угодно можно обмануть, если начать использовать грязные трюки. У вас претензия к Smalltalk за то, что он такой сякой, позволяет отправлять сообщения, не удосужевшись узнать, умеют ли его принять? Или к тому, что отдельные реализации плохо с этим справляются?
Батенька, вам к лицу ваш ник. Мы уже два дня пишем про ООП, и тут, БАХ!, внезапно не про ООП, а так, о природе, о кошечках, так получается? Это не серьёзно.
Внезапно, во фразе "В конце концов, полиморфизм без наследования интерфейсов не возможен." нет ни слова про ограничение на ООП. Впрочем, для ООП это тоже не верно. Один пример выше, второй легко правится под ООП следующим образом:
type ListWithTotal<'T when 'T : (static member (+) : 'T * 'T -> 'T)> =
class end
У вас претензия к Smalltalk за то, что он такой сякой, позволяет отправлять сообщения, не удосужевшись узнать, умеют ли его принять?
У меня вообще вообще нет претензий к Smalltalk, я просто пытаюсь понять, вы таки считаете его ООП-языком, или нет.
Хорошо, с этих пор я буду писать «вне ООП» каждый раз, как буду писать не об ООП.
> Впрочем, для ООП это тоже не верно. Один пример выше, второй легко правится под ООП следующим образом
Первый пример, в общем, — не ООП, а пережиток процедурного программирования. Только конкретная реализация конкретного языка позволит сказать, возможно ли эту ситуация обернуть в ООП (как если бы это были макросы, проверяющие подставляемые типы) или нет (если дёргается метод без проверок).
А шаблоны, мой милый собеседник, повторяюсь ещё раз, служат для генирации интерфейсов и\или реализации. То есть параметризуя шаблон вы получите новый интерфейс со своим именем и методами, своими или общими — в зависимости от параметризации самих методов.
> У меня вообще вообще нет претензий к Smalltalk, я просто пытаюсь понять, вы таки считаете его ООП-языком, или нет.
Ничего не изменилось.
первый пример, в общем, — не ООП, а пережиток процедурного программирования
Это почему?
А шаблоны, мой милый собеседник, повторяюсь ещё раз, служат для генирации интерфейсов и\или реализации.
Только это не шаблоны.
Ничего не изменилось.
Так Smalltalk — ООП-язык или нет?
А чем target.do() отличается от
typedef void (*pDo)(myStruct *target);
pDo do = (*pDo)(void*)(target.func);
do(target);
Требованием вызвать метод по имени? Но это не вопрос ООП, нигде не стоит обязательства хранить имя метода, и конкретные реализации могут заменить строки хешами или указателями. А больше ничем.
> Только это не шаблоны
А что?
> Так Smalltalk — ООП-язык или нет?
Да.
А чемtarget.do()
отличается отtypedef void (*pDo)(myStruct *target);
Отсутствием типа. Вообще.
А что?
Обобщенный класс.
Так Smalltalk — ООП-язык или нет?
Да.
О, прекрасно. Давайте запомним это утверждение.
Сразу вопрос: почему первый мой пример (который один в один реализуется на Smalltalk) — не ООП?
А что такое .Do() тогда? И что такое dynamic?
> Обобщенный класс.
То есть, шаблон.
> Сразу вопрос: почему первый мой пример (который один в один реализуется на Smalltalk) — не ООП?
>>Первый пример, в общем, — не ООП, а пережиток процедурного программирования. Только конкретная реализация конкретного языка позволит сказать, возможно ли эту ситуация обернуть в ООП (как если бы это были макросы, проверяющие подставляемые типы) или нет (если дёргается метод без проверок).
Подозреваю, что проверок нет, а значит, интерпретатор будет дёргать любой одноимённый слот\метод\ассептор. Конкретно это фича — не из ООП. И?
А что такое .Do() тогда?
Метод.
И что такое dynamic?
Ключевое слово, включающее DLR.
То есть, шаблон.
Нет. Не все обобщенные классы — шаблоны.
Подозреваю, что проверок нет, а значит, интерпретатор будет дёргать любой одноимённый слот\метод\ассептор. Конкретно это фича — не из ООП. И?
И то, что так работает весь Smalltalk. При любом вызове будет дергаться "одноименный метод" (и это, заметим, воспевается Кэем как фундаментальное качество ООП). Если эта фича не из ООП, то что в Smalltalk из ООП?
Вы послушайте, сами внимательно с четвёртого пункта. В нём он говорит, что в больших сложных системах не всегда удаётся произвести правильную декомпозицию, потому возникает сильная связность между классами и драконовские, порой, рекурсивные графы наследования, что плохо от слова пушнина. Это проблема ООП?
Это проблема дизайна. Решится ли она с помощью ФП? Нет, точно такая же проблема будет и в функциональных языках, она лишь немного сместится, когда нельзя будет, например, просто обернуть операцию в одну функцию, так как она зависит от слишком многих состояний слишком многих модулей, при этом копирование состояний будет не возможно в силу ограниченности объёма памяти и мутабельности части из них, а любое разбиение этой операции приведёт к размазыванию реализации, ухудшению удобочитаемости и усложнению поддержки. И это поведение не зависит, функционально ли мы подходим, объектно, структурно, агентно или ещё как-то. Если задача — пушнина, то и на выходе будет шуба из норок.
Elixir: Как выглядит ООП в функциональном языке?