Здравствуйте, Andrei N.Sobchuck, Вы писали:
D>>Так же выброс исключения это тоже не сообщение.
ANS>Ага. Не одно сообщение, а целая куча ANS>Если серьёзно, подозреваю, что зависит реализации (изначально ведь никаких исключений не было). В VisualWorks исключения реализованы средствами языка.
Ну в Smalltalk очень многое делается "средствами языка". Например сам Smalltalk, а точнее его метамодель рефлексивно определяется на самом Smalltalk. Есть статья Metaclasses and Reflection in Smalltalk. Там параллельно строят VM Smalltalk в лябда исчислении и рефлексивно определяют метамодель. Статья изрядно загрузная, математиками писанная, но мне лично, в свое время, понять Smalltalk изрядно помогла.
А что касается определнения в Smalltalk чего-нить "не средствами языка", так тут весь вопрос в отношении к примитивам. Если метод реализованный примитивом считаем "Smalltalk`овским", то вообще все средствами языка сделано.
Здравствуйте, eao197, Вы писали:
E>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
Неверная аналогия. Ручка у дипломата это скорее аналог интерфейса. А вот аналогом duck typing было бы, если бы алгоритм проверял не наличие ручки, а наличие чего то, что ручкой называется. Такой алгоритм при этом бы пытался точно так же унести дверь от туалета или дернуть стоп-кран в поезде.
Здравствуйте, AndrewVK, Вы писали:
E>>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
AVK>Неверная аналогия. Ручка у дипломата это скорее аналог интерфейса. А вот аналогом duck typing было бы, если бы алгоритм проверял не наличие ручки, а наличие чего то, что ручкой называется. Такой алгоритм при этом бы пытался точно так же унести дверь от туалета или дернуть стоп-кран в поезде.
Нет, я же специально сказал: "если эта штука похожа на чемодан и имеет ручку". Стоп-кран бы здесь не подошел.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Нет, я же специально сказал: "если эта штука похожа на чемодан и имеет ручку". Стоп-кран бы здесь не подошел.
Вот тем и отличается метод интерфейса от duck typing. Он говорит что это не просто ручка, а ручка для переноски, поскольку этот метод является частью интерфейса Переносимый. Более того, мы можем приспособить к чемодану две ручки, одну для переноски, а другую для просушивания на веревке, и, благодаря тому что ручки принадлежат разным интерфейсам, быть уверенным что переносить его будут только за одну ручку, а сушить на другой. В случае же duck typing нам остается только надеятся, что название метода на 100% отражает его предназначение.
Здравствуйте, eao197, Вы писали:
E>>>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
AVK>>Неверная аналогия. Ручка у дипломата это скорее аналог интерфейса. А вот аналогом duck typing было бы, если бы алгоритм проверял не наличие ручки, а наличие чего то, что ручкой называется. Такой алгоритм при этом бы пытался точно так же унести дверь от туалета или дернуть стоп-кран в поезде.
E>Нет, я же специально сказал: "если эта штука похожа на чемодан и имеет ручку". Стоп-кран бы здесь не подошел.
Вот тут, имхо, ты не прав (на что я тебе уже указывал).
В duck typing (несмотря на название) нет конструкций like-a, есть только can.
Т.е. мы не говорим
if(нечто like-a чемодан) ...
А вместо того говорим
if(нечто can взять-за-ручку) ...
Здесь вполне уместна аналогия АВК — что этот "алгоритм" будет хвататься за двери и стоп-краны.
Чтобы не хватался — надо сказать
if(нечто can взять-за-ручку && нечто can унести) ...
В этом случае от унесения не застрахован любой предмет с ручкой В том числе — дверь, если плохо прикреплена
Здравствуйте, AndrewVK, Вы писали:
E>>Нет, я же специально сказал: "если эта штука похожа на чемодан и имеет ручку". Стоп-кран бы здесь не подошел.
AVK>Вот тем и отличается метод интерфейса от duck typing. Он говорит что это не просто ручка, а ручка для переноски, поскольку этот метод является частью интерфейса Переносимый. Более того, мы можем приспособить к чемодану две ручки, одну для переноски, а другую для просушивания на веревке, и, благодаря тому что ручки принадлежат разным интерфейсам, быть уверенным что переносить его будут только за одну ручку, а сушить на другой. В случае же duck typing нам остается только надеятся, что название метода на 100% отражает его предназначение.
Так ведь, Андрей, в этом и все дело. Я почему заинтересовался duck typing-ом? Потому, что в реальной жизни нет интерфейсов, нет наследования. Тот же чемодан может играть как роль контейнера для багажа при переноске (тогда для него важна ручка), так и роль хранилища не нужных вещей (тогда не важно есть ли ручка), так и роль подставки. Все зависит от того, что мы хотим сделать и на что должен быть похож искомый нами объект.
ООП есть способ отражения реального мира в программный код. В статически типизированных языках поэтому нам приходится мыслить интерфейсами и их иерархией (наследованием). И если, скажем, нам нужна подставка, то мы описываем интерфейс подставки и требуем, чтобы классы наследовались от него (иначе их экземпляры нельзя будет использовать в качестве подставок). А это значит, что в нашей программе мы должны изначально предусматривать потребуются ли нам подставки вообще, а если потребуются, то что мы будем в их качестве использовать. И если мы в чем-то ошиблись (например, забыли унаследоваться в каком-то классе от интерфейса "подставка"), то производим рефакторинг. А если рефакторинг не возможен (скажем, приходится работать с классами из 3rd party библиотеки), то делать дополнительные обертки. А уж если нам потребуется совместить в одном классе совершенно разные интерфейсы (контейнер для багажа, хранилище не нужных вещей, подставка), то наследование класса от всех этих интерфейсов выглядит, прямо скажем, странно. Но хуже всего то, что эту иерархию приходится регулярно, как говорят у меня на родине, ператрахивать.
Но ведь в реальной жизни таких странностей нет. Они появляются при попытке использовать ООП в слишком сложных задачах. Имхо, это именно то, о чем говорил McSeem2.
Имхо, duck typing позволяет при проектировании и программировании придерживаться других правил: правил "похожести". Если какая-то сущность похожа на подставку, давайте будем использовать ее как подставку. Если похожа на хранилище ненужного хлама -- забъем ее под завязку. В этом, имхо, и разница между ООП и duck typing-ом. В ООП мы описываем похожести в виде интерфейсов и требуем их оформления и наследования. В duck typing-е мы просто находим похожести и используем их затем без наследования. Интерфейсы и там, и там существуют. Но подход к ним разный. Похожесть, например, в Ruby определяется не по наследованиию от чего-нибудь, а по наличию метода, реализующего нужную нам операцию. Поэтому, имхо, при проектировании в рамках duck typing-а мы просто-напросто опускаем такую часть, как построение иерархии наследования.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Зверёк Харьковский, Вы писали:
E>>Нет, я же специально сказал: "если эта штука похожа на чемодан и имеет ручку". Стоп-кран бы здесь не подошел.
ЗХ>Вот тут, имхо, ты не прав (на что я тебе уже указывал). ЗХ>В duck typing (несмотря на название) нет конструкций like-a, есть только can. ЗХ>Т.е. мы не говорим ЗХ>
ЗХ>if(нечто like-a чемодан) ...
ЗХ>
Так в том-то и дело, что lika-a в таком применении -- просто синоним is-a.
Под lika-a я как раз понимал серию "can" ("respond-to"):
if( нечто.respond_to?( "открыть" ) and
нечто.respond_to?( "поставить" ) and
нечто.respond_to?( "взять-за-ручку" ) ) ...
ЗХ>В этом случае от унесения не застрахован любой предмет с ручкой В том числе — дверь, если плохо прикреплена
Да. Именно так. В конце-концов то же самое произойдет и в реальной жизни, если мы попробуем человеку, ни разу не видевшему чемодана, в двух словах объяснить что это такое. Если мы не найдем точных слов, то вместо чемодана он нам дверь и принесет. Вместе с дверной коробкой. И миллицией на хвосте
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Так ведь, Андрей, в этом и все дело. Я почему заинтересовался duck typing-ом? Потому, что в реальной жизни нет интерфейсов, нет наследования.
Интерфейсы есть. Наследования действительно нет. Но, по большому счету, в реальной жизни нет и тех классов, что присутствуют в программах. Объектная модель программы это всего лишь модель, причем заточенная и оптимизированная под решение конкретной задачи. И, зачастую, эта модель очень слабо похожа на то, что можно пощупать руками.
E> Тот же чемодан может играть как роль контейнера для багажа при переноске (тогда для него важна ручка), так и роль хранилища не нужных вещей (тогда не важно есть ли ручка),
В мире ООП это означает что конкретный класс может реализовывать любой набор интерфейсов.
E>ООП есть способ отражения реального мира в программный код.
Модели, придуманной архитектором, а не реального мира. А уж откуда взялась эта модель — дело десятое.
E> В статически типизированных языках поэтому нам приходится мыслить интерфейсами и их иерархией (наследованием). И если, скажем, нам нужна подставка, то мы описываем интерфейс подставки и требуем, чтобы классы наследовались от него
Реализовывали будет более точным.
E> (иначе их экземпляры нельзя будет использовать в качестве подставок). А это значит, что в нашей программе мы должны изначально предусматривать потребуются ли нам подставки вообще, а если потребуются, то что мы будем в их качестве использовать. И если мы в чем-то ошиблись (например, забыли унаследоваться в каком-то классе от интерфейса "подставка"), то производим рефакторинг. А если рефакторинг не возможен (скажем, приходится работать с классами из 3rd party библиотеки), то делать дополнительные обертки. А уж если нам потребуется совместить в одном классе совершенно разные интерфейсы (контейнер для багажа, хранилище не нужных вещей, подставка), то наследование класса от всех этих интерфейсов выглядит, прямо скажем, странно. Но хуже всего то, что эту иерархию приходится регулярно, как говорят у меня на родине, ператрахивать.
А альтернатива какая? Использовать чемодан в качестве подставки, даже если он не рассчитан на это? А если он от этого сломается? Ты пойми, я не зря постоянно оговариваюсь что ОО-модель оптимизированна под конкретную задачу. В отличие от real-чемодана, который можно заюзать как угодно, конкретная ОО-модель чемодана может быть рассчитана только под конкретный набор операций, и попытка использовать ее иначе в лучшем случае приведет к ошибкам.
E>Имхо, duck typing позволяет при проектировании и программировании придерживаться других правил: правил "похожести". Если какая-то сущность похожа на подставку, давайте будем использовать ее как подставку. Если похожа на хранилище ненужного хлама -- забъем ее под завязку. В этом, имхо, и разница между ООП и duck typing-ом. В ООП мы описываем похожести в виде интерфейсов и требуем их оформления и наследования. В duck typing-е мы просто находим похожести и используем их затем без наследования.
Если перевести на русский, то мы фактически отказываемся от контракта (кстати, более последовательным была бы возможность не сопоставления по имени, а возможность опционального задания карты отображения одних методов на другие). И вот тут начинается самое интересное — либо мы вынуждены изучать чемодан на предмет внутренней структуры и прикидывать, не развалится ли он когда мы на него телевизор поставим, либо поставить и посмотреть что произойдет.
E> Интерфейсы и там, и там существуют. Но подход к ним разный. Похожесть, например, в Ruby определяется не по наследованиию от чего-нибудь, а по наличию метода, реализующего нужную нам операцию.
Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
E> Поэтому, имхо, при проектировании в рамках duck typing-а мы просто-напросто опускаем такую часть, как построение иерархии наследования.
Здравствуйте, AndrewVK, Вы писали:
E>> Тот же чемодан может играть как роль контейнера для багажа при переноске (тогда для него важна ручка), так и роль хранилища не нужных вещей (тогда не важно есть ли ручка),
AVK>В мире ООП это означает что конкретный класс может реализовывать любой набор интерфейсов.
Но привязываются то эти интерфейсы к классу посредством наследования. Кроме того сами интерфейсы могут наследоваться друг от друга. Как в Java, например.
E>> В статически типизированных языках поэтому нам приходится мыслить интерфейсами и их иерархией (наследованием). И если, скажем, нам нужна подставка, то мы описываем интерфейс подставки и требуем, чтобы классы наследовались от него
AVK>Реализовывали будет более точным.
Да, но ведь в Java/C++ "реализовывали" == "унаследовали". А в C# так же?
AVK>А альтернатива какая? Использовать чемодан в качестве подставки, даже если он не рассчитан на это? А если он от этого сломается?
А если при обращении к интерфейсу мы получим NotImplementedException?
AVK> Ты пойми, я не зря постоянно оговариваюсь что ОО-модель оптимизированна под конкретную задачу. В отличие от real-чемодана, который можно заюзать как угодно, конкретная ОО-модель чемодана может быть рассчитана только под конкретный набор операций, и попытка использовать ее иначе в лучшем случае приведет к ошибкам.
Согласен. Но ведь и с duck typing моделью будет то же самое.
E>>Имхо, duck typing позволяет при проектировании и программировании придерживаться других правил: правил "похожести". Если какая-то сущность похожа на подставку, давайте будем использовать ее как подставку. Если похожа на хранилище ненужного хлама -- забъем ее под завязку. В этом, имхо, и разница между ООП и duck typing-ом. В ООП мы описываем похожести в виде интерфейсов и требуем их оформления и наследования. В duck typing-е мы просто находим похожести и используем их затем без наследования.
AVK>Если перевести на русский, то мы фактически отказываемся от контракта (кстати, более последовательным была бы возможность не сопоставления по имени, а возможность опционального задания карты отображения одних методов на другие). И вот тут начинается самое интересное — либо мы вынуждены изучать чемодан на предмет внутренней структуры и прикидывать, не развалится ли он когда мы на него телевизор поставим, либо поставить и посмотреть что произойдет.
Так ведь в случае с интерфейсом "подставка" мы в той же ситуации: стеклянный журнальный столик не обязан выдержать телевизор, хоть и является подставкой.
E>> Интерфейсы и там, и там существуют. Но подход к ним разный. Похожесть, например, в Ruby определяется не по наследованиию от чего-нибудь, а по наличию метода, реализующего нужную нам операцию.
AVK>Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
Да. В этом есть проблема.
Но для конкретной задачи и конкретной предметной области проблемы устраняются путем тотального тестирования.
Может быть динамически типизированные языки так долго имели репутацию не надежных, из-за того, что не было моды на unit-тестинг.
E>> Поэтому, имхо, при проектировании в рамках duck typing-а мы просто-напросто опускаем такую часть, как построение иерархии наследования.
AVK>Наследование тут совершенно не при чем.
Странно, мне казалось что в ОО-дизайне одной из главных задач является выделение отношений наследования.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
AVK>>В мире ООП это означает что конкретный класс может реализовывать любой набор интерфейсов.
E>Но привязываются то эти интерфейсы к классу посредством наследования.
Реализации. И что?
E> Кроме того сами интерфейсы могут наследоваться друг от друга. Как в Java, например.
Могут. Наследование интерфейсов по сути констрейнт — если реализуешь интерфейс Х, то обязан реализовать всех его предков.
А что, это плохо?
AVK>>Реализовывали будет более точным.
E>Да, но ведь в Java/C++ "реализовывали" == "унаследовали".
В Java нет.
E> А в C# так же?
Так же как в Java — наследование и реализация это разные понятия.
AVK>>А альтернатива какая? Использовать чемодан в качестве подставки, даже если он не рассчитан на это? А если он от этого сломается?
E>А если при обращении к интерфейсу мы получим NotImplementedException?
Свидетельствует о неграмотном проектировании интерфейсов (МС этим частенько страдает). Если реализация части интерфейса имеет смысл, то значит этот интерфейс нужно разделить на два. Врочем, NotImplementedException все равно значительно лучше, чем если метод делает совсем не то что нужно и его сигнатура случайным образом совпала.
AVK>> Ты пойми, я не зря постоянно оговариваюсь что ОО-модель оптимизированна под конкретную задачу. В отличие от real-чемодана, который можно заюзать как угодно, конкретная ОО-модель чемодана может быть рассчитана только под конкретный набор операций, и попытка использовать ее иначе в лучшем случае приведет к ошибкам.
E>Согласен. Но ведь и с duck typing моделью будет то же самое.
Неа. Потому что в случае duck typing может случайно совпасть сигнатура, а вот в случае интерфейса мы должны явно специфицировать реализацию.
E>Так ведь в случае с интерфейсом "подставка" мы в той же ситуации: стеклянный журнальный столик не обязан выдержать телевизор, хоть и является подставкой.
В случае интерфейсов компилятор (по возможности) проконтроллирует наличие интерфейса. А, главное, если возможности нет, то в рантайме в вполне конкретном месте будет вполне конкретное исключение. Переходя к твоей аналогии — вместо неожиданно рассыпавшегося в случайный момент под телевизором чемодана имеем сработавшую сигнализацию с предупреждением о том, что телевизор на чемодан ставить нельзя.
AVK>>Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
E>Да. В этом есть проблема.
E>Но для конкретной задачи и конкретной предметной области проблемы устраняются путем тотального тестирования.
Ну вот, опять универсальная отмазка. Но, во-первых, это деньги (по словам людей, которые этим серьезно занимаются, от 25 до 40 процентов от стоимости проекта), во-вторых далеко не любой код можно оттестировать на 100% (банально комбинаций входных параметров и состояния может быть невообразимое количество), в-третьих остается возможность того что мы просто забудем покрыть все тестами (всевозможные code coverage tools тоже полной гарантии не дают).
И вместе с тем контроль контракта по публичному интерфейсу компилятор выполняет бесплатно. Думаю это стоит некоторого неудобства при кодировании (именно при кодировании, на архитектуру это влияет слабо).
E>Может быть динамически типизированные языки так долго имели репутацию не надежных, из-за того, что не было моды на unit-тестинг.
Бесплатный сыр бывает только в мышеловках. Об этом стоит всегда помнить.
E>>> Поэтому, имхо, при проектировании в рамках duck typing-а мы просто-напросто опускаем такую часть, как построение иерархии наследования.
AVK>>Наследование тут совершенно не при чем.
E>
E>Странно, мне казалось что в ОО-дизайне одной из главных задач является выделение отношений наследования.
Вопрос подхода. Я, к примеру, считаю что классическое ОО-наследование приносит больше вреда, нежели пользы и оправданно только для быстрого накидывания прототипов, а не для продакшн-кода.
Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>Чтобы не хватался — надо сказать ЗХ>
ЗХ>if(нечто can взять-за-ручку && нечто can унести) ...
ЗХ>
ЗХ>В этом случае от унесения не застрахован любой предмет с ручкой В том числе — дверь, если плохо прикреплена
Самое веселое (помним об ограниченности ОО-модели), что "унести" может отсутствовать в публичном интерфейсе. Тогда задача автоматического контроля даже в рантайме становится неразрешимой без нарушения инкапсуляции (и никакие unit-тесты, что характерно, гарантировать тоже ничего не смогут, поскольку успешность операции может быть плохо формализуема).
Здравствуйте, eao197, Вы писали:
E>либо в C# (могу ошибится в синтаксисе):
В С# 3.0 duck typing в нескольких местах есть. Например сокращенная инициализация массива:
IList<Bird> birds = new List<Bird> {penguin, eagle, hedgehog};
Преобразуется компилятором в код:
List<Bird> l = new List<Bird>();
l.Add(penguin);
l.Add(eagle);
l.Add(hedgehog);
IList<Bird> birds = l;
Так вот, метод Add подцепляется через тот самый duck typing. Почему так? Очень просто. С одной стороны нельзя привязываться к конкретным существующим интерфейсам, потому что фича сверхуниверсальная, с другой нельзя вводить свой, потому что тогда существующие коллекции не будут работать, с третьей стороны необходимо поддержать extension methods, которые принципиально не могут реализовывать интерфейсы. Но это мера вынужденная, а не некий универсальный принцип.
Здравствуйте, AndrewVK, Вы писали:
E>> Кроме того сами интерфейсы могут наследоваться друг от друга. Как в Java, например.
AVK>Могут. Наследование интерфейсов по сути констрейнт — если реализуешь интерфейс Х, то обязан реализовать всех его предков. AVK>А что, это плохо?
Я не говорил что это плохо. Просто сказал, что на уровне интерфейсов так же есть наследование.
E>>Да, но ведь в Java/C++ "реализовывали" == "унаследовали".
AVK>В Java нет.
E>> А в C# так же?
AVK>Так же как в Java — наследование и реализация это разные понятия.
Из-за того, что в Java есть ключевые слова extends и implements еще не значит, что это разные понятия. Указание implements в Java -- тоже самое, что в C++ наследование от абстрактного класса.
E>>Согласен. Но ведь и с duck typing моделью будет то же самое.
AVK>Неа. Потому что в случае duck typing может случайно совпасть сигнатура, а вот в случае интерфейса мы должны явно специфицировать реализацию.
А что делать, если в разных интерфейсах обнаруживаются методы с одинаковыми сигнатурами? open(), например.
E>>Так ведь в случае с интерфейсом "подставка" мы в той же ситуации: стеклянный журнальный столик не обязан выдержать телевизор, хоть и является подставкой.
AVK>В случае интерфейсов компилятор (по возможности) проконтроллирует наличие интерфейса. А, главное, если возможности нет, то в рантайме в вполне конкретном месте будет вполне конкретное исключение. Переходя к твоей аналогии — вместо неожиданно рассыпавшегося в случайный момент под телевизором чемодана имеем сработавшую сигнализацию с предупреждением о том, что телевизор на чемодан ставить нельзя.
Мы получим то же самое исключение "нечто рассыпалось от слишком большой нагрузки".
Но суть в том, что исключение мы получим в run-time, и интерфейсы, которые нас должны были предохранять от попытки использовать предметы не по назначению, нас не спасли.
AVK>>>Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
E>>Да. В этом есть проблема.
E>>Но для конкретной задачи и конкретной предметной области проблемы устраняются путем тотального тестирования.
AVK>Ну вот, опять универсальная отмазка. Но, во-первых, это деньги (по словам людей, которые этим серьезно занимаются, от 25 до 40 процентов от стоимости проекта), во-вторых далеко не любой код можно оттестировать на 100% (банально комбинаций входных параметров и состояния может быть невообразимое количество), в-третьих остается возможность того что мы просто забудем покрыть все тестами (всевозможные code coverage tools тоже полной гарантии не дают).
+1.
Согласен. Поэтому динамические языки вряд ли сильно угрожают статически типизированным языкам.
AVK>И вместе с тем контроль контракта по публичному интерфейсу компилятор выполняет бесплатно. Думаю это стоит некоторого неудобства при кодировании (именно при кодировании, на архитектуру это влияет слабо).
Здравствуйте, AndrewVK, Вы писали:
E>>Имхо, duck typing позволяет при проектировании и программировании придерживаться других правил: правил "похожести". Если какая-то сущность похожа на подставку, давайте будем использовать ее как подставку. Если похожа на хранилище ненужного хлама -- забъем ее под завязку. В этом, имхо, и разница между ООП и duck typing-ом. В ООП мы описываем похожести в виде интерфейсов и требуем их оформления и наследования. В duck typing-е мы просто находим похожести и используем их затем без наследования.
AVK>Если перевести на русский, то мы фактически отказываемся от контракта (кстати, более последовательным была бы возможность не сопоставления по имени, а возможность опционального задания карты отображения одних методов на другие). И вот тут начинается самое интересное — либо мы вынуждены изучать чемодан на предмет внутренней структуры и прикидывать, не развалится ли он когда мы на него телевизор поставим, либо поставить и посмотреть что произойдет.
От контратка прямо сразу никто не отказывается. Изменяется область применимости (scope) контракта, все методы названные одинакого должны выполнять один контракт. Это похоже на перегрузку, если в одно интерфейсе (или классе) есть методы с одинаковым именем, но разными типами параметров, естественно ожидать, что делают эти методы, что-то очень похожее. С duck typing тоже самое, надо несколько поменять стиль программирования, и следить, что бы название не только отражало что метод делает, но и не конфликтовало с другими методами.
Тут вообще-то нужна дополнительная поддержка от языка. Т.к. контрактов на методы может быть много, то и названий должно быть много, и в языке должно быть естественно использование длинных имен методов (речь конечно не про названия f, g, и т.п.). В Smalltalk эта проблема решена, имена методов естественным образом длинные, и проблем с пересечением названий для разных контрактов достаточно мало, что бы не мешать. В языках типа C (не из какого языка исходно пошла традиция писать параметры отдельно от имени в скобках) с duck-typing возможны существенные проблемы, в частности сделать большой проект без типов может привести к большой путанице. Свой вклад в путаницу внесут и библиотеки, исходно не расчитаные на применение без типов и пересекающиеся по названиям контрактов с чем попало.
Если в этом свете посмотреть на пример про предметы с ручкой то с duck-typing можно записать алгоритм типа
взять ручку и потянуть
Применительно к чемодану он будет поднимать, к двери — открывать, а стоп-кран дергать. Т.е. получается очень широко применимый алгоритм, что приятно. Но вот если мы напишем алгоритм
взять ручку и написать "привет"
То тут мы огребем странное поведение например с дверью.
E>> Интерфейсы и там, и там существуют. Но подход к ним разный. Похожесть, например, в Ruby определяется не по наследованиию от чего-нибудь, а по наличию метода, реализующего нужную нам операцию.
AVK>Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
Согласен в том смысле, что duck-typing стоит вводить в язык в версии 1.0, когда на нем еще ничего не написано, существенно закладываясь на типы. Если его вводить позже, то получим лишь средство для экспериментального кода (как я предлагал тут
) и всяких хаков.
E>> Поэтому, имхо, при проектировании в рамках duck typing-а мы просто-напросто опускаем такую часть, как построение иерархии наследования.
AVK>Наследование тут совершенно не при чем.
Угу. Мы опускаем, а все что было создано до нас и не нами на иерархии очень надеется.
Здравствуйте, eao197, Вы писали:
E>Я не говорил что это плохо. Просто сказал, что на уровне интерфейсов так же есть наследование.
Есть. Только сходство с наследованием классов чисто внешнее. По сути же это просто такое ограничение.
AVK>>Так же как в Java — наследование и реализация это разные понятия.
E>Из-за того, что в Java есть ключевые слова extends и implements еще не значит, что это разные понятия.
Из-за того что эти операции имеют разный смысл.
E> Указание implements в Java -- тоже самое, что в C++ наследование от абстрактного класса.
Нет, не то же самое. Все намного глубже и интереснее. На самом деле наличие реализовываемого интерфейса означает что компилятор создает специальную таблицу, interface map, которая отображает методы интерфейса на методы класса. При этом сам класс от интерфейса ничего не наследует, только приобретает соотв. метаданные. Поэтому в Java и применено другое ключевое слово (семантика операции другая), а в C#, хоть реализация синтаксически и выглядит так же как наследование, в документации везде упоминается термин implement.
AVK>>Неа. Потому что в случае duck typing может случайно совпасть сигнатура, а вот в случае интерфейса мы должны явно специфицировать реализацию.
E>А что делать, если в разных интерфейсах обнаруживаются методы с одинаковыми сигнатурами? open(), например.
В C# есть такая штука как явная реализация интерфейса. В этом случае реализующий метод является приватным и имеет имя ИмяИнтерфейса.ИмяМетодаИнтерфейса .
AVK>>В случае интерфейсов компилятор (по возможности) проконтроллирует наличие интерфейса. А, главное, если возможности нет, то в рантайме в вполне конкретном месте будет вполне конкретное исключение. Переходя к твоей аналогии — вместо неожиданно рассыпавшегося в случайный момент под телевизором чемодана имеем сработавшую сигнализацию с предупреждением о том, что телевизор на чемодан ставить нельзя.
E>Мы получим то же самое исключение "нечто рассыпалось от слишком большой нагрузки".
Нет. Прелесть исключений в том что они детерминированы и локализованы. Т.е. осыпаться будет всегда и строго в том месте, где имеется проблема.
E>Но суть в том, что исключение мы получим в run-time,
Не забывай о том, что львиная доля проверок выполняется в compile time.
AVK>>И вместе с тем контроль контракта по публичному интерфейсу компилятор выполняет бесплатно. Думаю это стоит некоторого неудобства при кодировании (именно при кодировании, на архитектуру это влияет слабо).
E>Мне симпатична идея, которая реализована в Heron: Unintrusive Retroactive Polymorphism
Здравствуйте, AndrewVK, Вы писали:
E>>Я не говорил что это плохо. Просто сказал, что на уровне интерфейсов так же есть наследование.
AVK>Есть. Только сходство с наследованием классов чисто внешнее. По сути же это просто такое ограничение.
Правда? А зачем тогда наследование интерфейсов вообще? Не для того ли, чтобы показать, что объект, реализующий интерфейс A (т.е. is-a A) так же реализует интерфейс B (т.е. is-a B) /A производен от B/.
E>> Указание implements в Java -- тоже самое, что в C++ наследование от абстрактного класса.
AVK>Нет, не то же самое. Все намного глубже и интереснее. На самом деле наличие реализовываемого интерфейса означает что компилятор создает специальную таблицу, interface map, которая отображает методы интерфейса на методы класса.
Тоже самое, имхо, происходит в C++ когда мы наследуемся от абстрактного класса без данных. Таблицу виртуальных функций ведь не я сам строю.
AVK>>>Неа. Потому что в случае duck typing может случайно совпасть сигнатура, а вот в случае интерфейса мы должны явно специфицировать реализацию.
E>>А что делать, если в разных интерфейсах обнаруживаются методы с одинаковыми сигнатурами? open(), например.
AVK>В C# есть такая штука как явная реализация интерфейса. В этом случае реализующий метод является приватным и имеет имя ИмяИнтерфейса.ИмяМетодаИнтерфейса .
Здорово.
AVK>>>В случае интерфейсов компилятор (по возможности) проконтроллирует наличие интерфейса. А, главное, если возможности нет, то в рантайме в вполне конкретном месте будет вполне конкретное исключение. Переходя к твоей аналогии — вместо неожиданно рассыпавшегося в случайный момент под телевизором чемодана имеем сработавшую сигнализацию с предупреждением о том, что телевизор на чемодан ставить нельзя.
E>>Мы получим то же самое исключение "нечто рассыпалось от слишком большой нагрузки".
AVK>Нет. Прелесть исключений в том что они детерминированы и локализованы. Т.е. осыпаться будет всегда и строго в том месте, где имеется проблема.
Точно так же и в duck typing. Как только ставим на подставку груз больше расчетного -- получаем исключение. Все то же самое.
E>>Но суть в том, что исключение мы получим в run-time,
AVK>Не забывай о том, что львиная доля проверок выполняется в compile time.
Если duck typing встраивается в статически типизированный язык, то получаем те же самые проверки.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Dyoma, Вы писали:
D>От контратка прямо сразу никто не отказывается.
Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
D> Изменяется область применимости (scope) контракта, все методы названные одинакого должны выполнять один контракт.
Кому должны? Компилятор не в состоянии проконтроллировать это. А значит мы задачу по контролю семантической совместимости возлагаем на программиста.
D> Это похоже на перегрузку, если в одно интерфейсе (или классе) есть методы с одинаковым именем, но разными типами параметров, естественно ожидать, что делают эти методы, что-то очень похожее.
Совсем не похоже. Перегрузка сосредоточена внутри одного класса, так что если ты там что то напортачил, то сам дурак. А вот duck typing взаимодействует с чужим классом, который не обязан знать ничего о том как ты интерпретируешь конкретную сигнатуру. Говоря проще — в первом варианте инкапсуляция не нарушается, а во втором нарушается.
D>Тут вообще-то нужна дополнительная поддержка от языка. Т.к. контрактов на методы может быть много, то и названий должно быть много, и в языке должно быть естественно использование длинных имен методов (речь конечно не про названия f, g, и т.п.).
Это все подпорки. Гарантию несовпадения тебе дадут только гуиды. Только вот с интерфейсами оно как то проще.
D> В Smalltalk эта проблема решена, имена методов естественным образом длинные, и проблем с пересечением названий для разных контрактов достаточно мало, что бы не мешать.
От это вряд ли. Видишь ли, тут проблема не столько в том, что методы случайно совпали по сигнатуре, сколько в том что разные программисты могут интерпретировать одну и ту же сигнатуру по разному. Что бы этого не произошло нужно конкретные сигнатуры описывать формально, с указанием в комментариях/документации что под этой сигнатурой понимается. Чувствуешь к чему пришли?
D>Если в этом свете посмотреть на пример про предметы с ручкой то с duck-typing можно записать алгоритм типа D>
D>взять ручку и потянуть
D>
D>Применительно к чемодану он будет поднимать, к двери — открывать, а стоп-кран дергать. Т.е. получается очень широко применимый алгоритм, что приятно.
И совершенно бессмысленный, что совсем неприятно.
AVK>>Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
D>Согласен в том смысле, что duck-typing стоит вводить в язык в версии 1.0, когда на нем еще ничего не написано, существенно закладываясь на типы.
Наоборот. Если проектировать с нуля, то duck typing совершенно бесполезен. Он оправдан только если мы не можем менять существующие наработки. Об этом я применительно к C# 3.0 в этом топике уже писал.
AVK>>Наследование тут совершенно не при чем.
D>Угу. Мы опускаем, а все что было создано до нас и не нами на иерархии очень надеется.
Помимо наследования есть масса паттернов для реюза кода.
Здравствуйте, AndrewVK, Вы писали:
D>>От контратка прямо сразу никто не отказывается.
AVK>Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
Да нет, как раз в том и суть duck typing, что это уже и не ОО.
Поэтому оценивать duck typing с точки зрения ОО не совсем корректно. Так же и ООП с точки зрения duck typing.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.