Здравствуйте, eao197, Вы писали:
E>Есть такая штука: Duck typing. В языках, вроде Smalltalk, Python и Ruby она активно используется.
За Python и Ruby не скажу — дела с ними не имел, а Smalltalk — это одна сплошная "утиная история", подругому на нем писать нету синтаксических возможностей.
E>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
Типизация и ОО — вещи абсолютно ортогональные. ОО — способ смотреть на мир. С точки зрения ОО весь мир — объекты, с все взаимодействия между объектами — посылка сообщений. О типизации ОО вообще ничего не говорит.
Типизация бывает статической и динамической.
Статическая это вид верификации. На код накладываются дополнительные ограничения и компилятор их проверяет. Выявляет некоторые ошибки до старта программы и дает некоторые гарантии. В частности, компилятор гарантирует, что объект обязался понимать посланное ему сообщение (там где отсутствует явное приведение типов).
Исторически статическая типизаия использовалась как способ дать компилятору подскази для построения эффективного кода. Например, С++ компилятор, строит vtbl для класса размером не на все возможные (в системе) сообщения, а только на определенные в этом классе. В Smalltalk, например, такой оптимизации не делается. По этому если Smalltalk объект получает "непонятное" для него сообщение, то он кидает исключение, а C++ объект начинает вести себя непредсказуемым образом (что почти всегда приводит к GPF, а иногда к порче данных или странному поведению).
Динамическая типизация это просто наличие у объекта такого свойства как тип. Отсутвие такого свойства никак не мешает ему быть объектом, т.е. реагировать на сообщения (пример — Self).
Более того, типизация несколько мешает ОО. Чистое объектное видение мира подразумевает, что любой объект потенциально может отреагировать на любое сообщение. Или другими словами есть только объекты и объекты не различаются ничем, кроме своего поведения. Например, к каждому человеку можно подойти с вопросом "Сколько ты будешь делать?" с параметром ТЗ. На что можно получить в ответ "N дней", "Чего???" или по зубам . Но коллапса с миром не случит в любом случае.
E>Еще есть несколько интересных обсуждений: Следующий язык программирования
E>Есть такой тип object. С ним можно вытварять очень много. Есть так же рефлексия с помощью которой хочть черта лысого можно динамически вызвать. Но C# — это статически типизированный язык. Если хотити динамики, то их есть у меня. Есть VB.NET с той самой "утиной типизацией", кстати в 9.0 с ней даже по круче будет. Там придумали "утиные интерфесы" — т.е. описываешь интерфейс как "утиный" и приводишь в рантайме к нему любой объект... если у объектоа методы совпадают, то можно вызвать методы через утиный интерфейс. Что это дает? А тот самый комплит ворд и подсказки среды, плюс еще возможность обнаружить ошибку при инициализации ссылки на интерфейс, а не при вызове 101-вого метода. Далее создали динамические переменные. Когда можно намисать та:
Это еще один пример как можно совмещать прелести верификации с ОО.
:
E>[q]
E>Java/C# не являются ни развитием, ни "осознанием ошибок" C++. Они взяли наихудшую парадигму из языка и возвели ее в степень догмы. А именно — идею наследования. Наследование — это самая большая провокация в индустрии. Ни в каком моделировании наследования не существует и в реальной жизни тоже — ни в электронике, ни в бухгалтерии, ни где бы то ни было еще. Есть одно — генеология. Но это не имеет ни малешего отношения к тому, что называется наследованием в программировании. Все эти многоэтажные иерархии классов только усложняют жизнь, вместо того, чтобы упрощать. И служат они одной единственной цели — раздувать и дальше этот мыльный пузырь и продолжать кормить миллиарды индусов. В результате этой идеи возникла дутая индустрия, не обеспеченная никакими реальными активами. А вся мировая практика говорит о том, что рано или поздно этот пузырь лопнет.
С этой мыслью, я не спорю, но она imho к вопросу отношния не имеет. Скорее к вопросу, какие парадигмы есть возможность сделать mainstream.
E>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
"Утиные истории" появились задолго до ОО, например тот же Lisp.
Ёлы, плалы. Ну, когда же ты наконце научишся приводить примры на 2-4 строчки кода? Зачем ты снова залудил гору кода, ну, не будет в нее никто вникать. Не надейся, что сможешь стать вторым Толстым.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, eao197, Вы писали:
E>Потому, что мы получили это исключение в результате действия "поставить чемодан на опору". А если бы соседский кот туда не нассал, может и выдержал бы, а так -- подгнил чуточку.
ДА какое исключение? Речь идет о том, что ты получишь compile-time error! Потому, что чемодан не работает как подставка. Точка.
Если ты попытаешься привести чемодан динамически, то получишь исключение ровно в том месте, где ты попытался его привести. А не при применении.
Вообще, я вижу у тебя отсутствие понимания двух вещей:
1. Наследование и реализация интерфейсов — разные вещи. То, что в C++ это выглядит одинаково, лучше считать случайным совпадением. Рекомендую как можно быстрее освоить разницу между этими понятиями. Иначе твои представления об ООП не будут соответствовать современному уровню.
2. Помимо наличия самой ошибки, важны три фактора:
— последствия. Хуже всего: недетерминированное поведение в продакшн системе. Лучше всего: запись в лог и выполнение корректной fallback стратегии
— место проявления. Хуже всего: ошибка обнаруживается неопределенно далеко от того места, где она сделана. Лучше всего: ошибка обнаруживается непосредственно там, где ее можно исправить
— время проявления. Хуже всего: ошибка проявляется иногда, при трудновоспроизводимом стечении обстоятельств. Лучше всего: есть детерминированный момент, когда ошибка, если есть, заведомо себя проявит.
Так вот, при дак тайпинге ошибка несоответствия семантики расположена в самом темном углу этого кубика. Потому, что
а) совершенно неизвестно, к чему она приведет. Метод "крякни" может спокойно сделать свою работу. И только после расследования и суда нам станет известно, что "крякнуть" означало совсем не то.
б) проявляется она вовсе не там, где мы сохранили ссылку на псевдоутку. А, самое раннее, при вызове метода "крякни".
в) момент проявления совершенно неопределен.
При использовании настоящих интерфейсов, мы либо получим ошибкупри компиляции и именно в том месте, где мы присваиваем хакера в слот для утки, либо в рантайме — в том же месте. При этом в рантайме мы получим детерминированное поведение, которое не приведет нас в тюрьму.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, eao197, Вы писали:
E>Есть небольшое отличие: динамические языки проще адаптировать к некоторым условиям. Например, нужно прочитать из потока заголовок, определить, сколько байт нужно пропустить, а затем прочитать еще несколько байт. Если потоком является файл, то можно просто сделать seek, а если потоком является сокет, то пропускаемые данные нужно читать: E>
Уникально мерзопакостный вариант
Для читателя кода — stream.respond_to( "seek" ) — выглядит как грязный хак.
Код должен (в идеале, естественно) читаться как постановка задачи.
С этой точки зрения, финальный вариант на C++
E>template< class T >
E>void load_header_and_data( T & stream )
E>{
E> Header h = load_header( stream );
E> seek( stream, h.get_length() );
E> load_data( stream );
E>}
E>
...читается существенно лучше. Другое дело, что для этого тебе потребовалось неимоверное количество усилий. Однако же. Ты реализовывал не свою постановку задачи "Если потоком является файл, то можно просто сделать seek, а если потоком является сокет", а другую постановку задачи "если у объекта, каким бы он ни был, есть метод seek, то..."
Наиболее изящным решением на C++ было бы — либо А:
template<typename T> seek(T,int);
template<> seek<File>(File _f, int _bytes);
template<> seek<Soket>(Soket _s, int _bytes);
либо Б:
class File
{
...
void skip(int _bytes){seek(_bytes);}
...
}
class Socket
{
...
void skip(int _bytes){read(_bytes);}
...
}
А твое решение (как на Руби, так и на С++) совершенно нерасширяемо — завтра у тебя появится еще один тип потока, у которого надо будет вызвать вообще функцию, скажем, move, и что?
def load_header_and_data( stream )
h = load_header( stream )
if stream.respond_to( "seek" )
stream.seek( h.get_length() )
else if stream.respond_to( "move" ) //и такие хаки - еще в 18 местах...
stream.move( h.get_length() )
else
stream.read( h.get_length() )
end
load_data( stream )
end
Есть такой тип object. С ним можно вытварять очень много. Есть так же рефлексия с помощью которой хочть черта лысого можно динамически вызвать. Но C# — это статически типизированный язык. Если хотити динамики, то их есть у меня. Есть VB.NET с той самой "утиной типизацией", кстати в 9.0 с ней даже по круче будет. Там придумали "утиные интерфесы" — т.е. описываешь интерфейс как "утиный" и приводишь в рантайме к нему любой объект... если у объектоа методы совпадают, то можно вызвать методы через утиный интерфейс. Что это дает? А тот самый комплит ворд и подсказки среды, плюс еще возможность обнаружить ошибку при инициализации ссылки на интерфейс, а не при вызове 101-вого метода. Далее создали динамические переменные. Когда можно намисать та:
a = "SomeMethodName"
...
b.a(x, y)
за синтаксис не ручвюсь, но смысл думаю понятен.
Но это все из другой оперы. Это все для скриптов. А в нормальных ОО-программах типизация должна быть типзацией, а не утиными историями.
Java/C# не являются ни развитием, ни "осознанием ошибок" C++. Они взяли наихудшую парадигму из языка и возвели ее в степень догмы. А именно — идею наследования. Наследование — это самая большая провокация в индустрии. Ни в каком моделировании наследования не существует и в реальной жизни тоже — ни в электронике, ни в бухгалтерии, ни где бы то ни было еще. Есть одно — генеология. Но это не имеет ни малешего отношения к тому, что называется наследованием в программировании. Все эти многоэтажные иерархии классов только усложняют жизнь, вместо того, чтобы упрощать. И служат они одной единственной цели — раздувать и дальше этот мыльный пузырь и продолжать кормить миллиарды индусов. В результате этой идеи возникла дутая индустрия, не обеспеченная никакими реальными активами. А вся мировая практика говорит о том, что рано или поздно этот пузырь лопнет.
Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Статическая типизации и "современный" ООП применяются повсемесно. Но повышения надежности программ и сокращения сроков разработки не происходит. Duck typing применяется гораздо реже, многие из моих оппонетнов в данном топике, вероятно, вообще его не применяют. Но при этом пытаются доказать, что duck typing менее надежен, чем статическая типизация и интерфейсы. Может быть вы и правы.
Ты зря думаешь что этот duck typing никто не применял и не применяет. В свое время, когда я писал на Java, я этот duck typing применял довольно широко (при помощи рефлекшена). А вот в C# я от него постепенно полностью отказался? Знаешь почему? Потому что этот duck typing приводит к слишком сильным связям в программе. И так перегруженная функционалом сигнатура перегружается еще больше (не говоря уж о том, что перестают работать автоматические рефакторинги). Мне, при разработке класса, приходится помнить о том, что какая то внешняя механика ожидает такую и именно такую сигнатуру и просто так ее трогать нельзя. Простейшее добавление еще одного параметра превращается в танцы с бубном, когда либо публичный метод надо перегружать (только ради сторонней механики), либо править этот метод во всех спользуемых сторонней механикой классов (а это тоже непросто определить).
Поэтому в .NET я в таких случаях использую атрибут. Т.е. привязка к методу происходит не по его сигнатуре, а по наличию специального, только для данной механики созданного атрибута.
Теперь что касается чужих классов, которые я поменять не могу. Для работы с такими классами, помимо создания оберток, есть еще масса способов. Например можно просто отнаследоваться. Или декларативно описать связь где нибудь вовне.
Здравствуйте, eao197, Вы писали:
E>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
Ну, возможно все-таки исчерпала. Статически типизированное ООП само по себе — штука очень простая. После длительного шатания из стороны в сторону все-таки пришли к более-менее единому знаменателю в виде классов/интерфейсов.
Дальнейшее развитие ООП-мейнстрима почему-то идет по пути
а) встраивания паттернов в язык — т.е. развитие над-ООП надстроек
б) введение ортогональных парадигм — например, generics.
Наверное, как раз оттого, что улучшать ООП уже некуда.
Duck typing, в принципе, не обязан работать динамически. Его можно было бы ввести, например, в constraintы generic. Т.е. вместо введения интерфейса ICountable { int Count { get; } } и заставления всех мыслимых параметров шаблона явно от него наследоваться при помощи
class SmartType<T>
where T: ICountable
{
public static int q(T param) { return t.Count;}
}
мы бы вводили утиный интерфейс ICountable, который бы вынуждал выполнять биндинг на этапе инстанцирования дженерика. Вся информация в принципе есть; особой тяжести здесь нету — операция выполняется единожды для каждого типа.
В отличие от, скажем, приведения произвольного object к нашему интерфейсу, которое не удастся сделать привычным ловким методом дотнета.
... << RSDN@Home 1.1.4 stable rev. 510>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Но это все из другой оперы. Это все для скриптов. А в нормальных ОО-программах типизация должна быть типзацией, а не утиными историями.
E>
Мне, ортодоксальному поклоннику строгой типизации, эти утиные истории кажутся слишком хлипкой конструкцией. Сначала, может быть, это и работает, но потом могут возникать проблемы. Например:
"Утиный" код менее очевиден. Не хотел бы я брать на доработку и сопровождение код, в котором Duck typing применён массово.
Чем больше в системе всяких явно не описанных "соглашений", "умолчаний", "подразумеваний" и т.п., тем меньше у неё шансов добраться до финального релиза. Может быть, я что-то неправильно понял, но вся мощь "утиных историй" проявляется тогда, когда язык программирования не заставляет нас предельно чётко и скрупулёзно описывать понятие "Утка". Один программер что-то там себе подразумевает, а его товарищ может подразумевать что-то чут-чуть другое. Один из них, например, может быть свято уверен, что утки в принципе не умеют летать.
E>
Они взяли наихудшую парадигму из языка и возвели ее в степень догмы. А именно — идею наследования. Наследование — это самая большая провокация в индустрии.
E>
Одобрямс!
На мой взгляд, "утиные истории" — это весьма неуклюжая попытка обойти "деревянную" сущность иерархий наследования. И даже когда язык позволяет делать множественное наследование, проблема всё равно окончательно не снимается, а всего лишь загоняется вглубь.
Пару лет назад, размышляя над этими проблемами, я дошёл до идеи множественной классификации (см. здесь). Лекарство, конечно, слишком сильнодействующее, но я до сих пор не разуверился в том, что рецепт выписан правильно.
Здравствуйте, eao197, Вы писали:
ЗХ>>Для читателя кода — stream.respond_to( "seek" ) — выглядит как грязный хак. ЗХ>>Код должен (в идеале, естественно) читаться как постановка задачи.
E>Ок. Переписываем: E>
...
E>
E>Код load_header_and_data стал понятнее. А хак, хоть и спрятанный, остался.
Я предвидел такой ответ
ЗХ>>Однако же. Ты реализовывал не свою постановку задачи "Если потоком является файл, то можно просто сделать seek, а если потоком является сокет", а другую постановку задачи "если у объекта, каким бы он ни был, есть метод seek, то..."
E>Однако же, это на первый взгляд так, если смотреть с позиции "is-a". Тогда действительно, файлом для нас будет что-то, что есть File (производное от File), а сокетом -- что-то производное от Socket. E>Если же посмотреть со стороны duck types, то разница между Socket-ом и File в том, что в File можно переставить позицию, а в Socket-е -- нет. И тогда мы получаем возможность за "сущность, у которой есть seek" спрятать не только File (то, о чем мы думали вначале), но и какой-нибудь MemoryBlock, или SmartCardReader. Соответственно за сокет мы можем выдать не только Socket, но и UIDGenerator или IOController.
Погоди! Но ведь в постановке задачи нет ничего о средствах реализации (то есть там не сказано "если поддерживается концепция is-a...").
Тем не менее, я, скажем, могу представить случай, в котором функция seek у объекта будет, но, скажем, заведомо неэффективная (или и вовсе — с тем же именем, но с другим смыслом).
Концептуально (в псевдо-псевдо коде) задача решается вот так:
здесь возникает философский вопрос: а кто должен знать нужный способ? Мне (пока) наиболее изящным и, скажем так, прямым решением кажется концепция traits:
traits< stream >.move_forward(stream, bytes);
Здесь и интерфейс stream не раздувается, но и решение принимается "тем, кто знает как решать".
Грубо говоря, если появится еще один тип потока (предположенный мной), у которого есть функция seek, но смысл ее другой, а сдвигаться вперед надо функцией move — то нужно всего лишь описать traits для типа этого потока, не меняя клиентский код.
Здравствуйте, eao197, Вы писали:
E>Есть такая штука: Duck typing. В языках, вроде Smalltalk, Python и Ruby она активно используется.
E>Еще есть несколько интересных обсуждений: Следующий язык программирования
Рассмотрим типичную ситуацию. Есть проект, живет он давно и понаделано за это время не мало. Стоит задача прикрутить фичу. И так получается, что фича может быть удобно сделана полагаясь на уже существующие абстрастные методы определенные в разных классах. К примеру, задача может выглядить так: есть список разнородных объектов, на реализовать еще одно полезное действие, применимое к некоторым из них (типа в IDE есть список из классов\методов\филдов\... хотим сделать переименование). И все было бы просто, если бы эта новая фича гладко ложилась на существующую архитектуру. Ан-нет — не ложится, нужны новые интерфейсы и местами новые методы.
Что делаем без уток:
1. Заводим интерфейс(ы), который должны бы поддерживать все элементы списка.
2. Пишем, что соотвествующие классы его поддерживают.
3. Пишем фичу. По мере написания добавляем в интерфейс необходимые методы и имплементим их в классах, где раньше они были не нужны, а теперь понадобились.
4. Переодически запускаем (возможно приложение, возможно тесты) и смотрим, что получается иногда глазами, а иногда дебагером. Из увиденного делаем выводу, выясняем тонкие подробности и возвращаемся к пункту 3.
Иногда натыкаемся на некоторые особенности некоторых реализаций, которые проваливают, казалось бы прямое красивое решение. Иногда фиксим и выпрямляем, а иногда не получается. Когда не получается приходится искать другое решение, заводить другие методы.
Теперь как с утками:
Первые два пункта смело пропускаем, новую фичу без типизации.
На 3 пункте имеем упрощение жизни, за счет не добавления методов в классы, в которые их довавить тяжело. Все компилится и без них. Так же в нашем распоряжении появляется возможность быстро пробовать решения и быстро обламываться, если они не работают. При этом можно не отвлекаться на вставку заглушек во все классы (даже если IDE эти заглушки вставит более-менее сама мысль может немного потеряться). И, что важнее, не забываем эти заглушки убирать, когда оказалось, что они не нужны (делать будем по-другому).
Вот так прыгаем между 3 и 4, пока не получаем, работающую или почти работающую фичу.
Здесь уток выкидываем, а точнее переходим к пункту 1, а потом 2 — вписываем типы. А компилятор проверяет, что мы не пропусти ни один класс и везде все необходимые методы есть.
В общем еще одина изложение довольно старой идеи — дизайн удобнее и легче делать, когда уже есть код на этом дизайне построенный, чем гадать, что понадобится, а что нет.
Более того, если охота погадать заранее, то пожалуйста, пунты 1 и 2 выполняем ровно на столько на сколько гадать кажется эффективным и полезным.
E>
E>за синтаксис не ручвюсь, но смысл думаю понятен.
E>Но это все из другой оперы. Это все для скриптов. А в нормальных ОО-программах типизация должна быть типзацией, а не утиными историями.
Получаем удобство утиных историй на стадии разработки-исследования, а потом, когда маршрут между граблями уже проложен, делаем из них "нормальную ОО-программу". При этом почти не делаем работу которую заведомо собираемся выкидывать (даже если граблей не оказалось).
E>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
Такое использование утиных историй поможет решить проблемы ООП совмещенного со строгой типизацией.
E>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
Скорее утиные истории поддерживают более чистое объектное мышление, чем статическая типизация. Вот объект, я хочу послать ему это сообщение, а какого он должен быть типа решим немного позже.
Здравствуйте, Andrei N.Sobchuck, Вы писали:
D>>Так же выброс исключения это тоже не сообщение.
ANS>Ага. Не одно сообщение, а целая куча ANS>Если серьёзно, подозреваю, что зависит реализации (изначально ведь никаких исключений не было). В VisualWorks исключения реализованы средствами языка.
Ну в Smalltalk очень многое делается "средствами языка". Например сам Smalltalk, а точнее его метамодель рефлексивно определяется на самом Smalltalk. Есть статья Metaclasses and Reflection in Smalltalk. Там параллельно строят VM Smalltalk в лябда исчислении и рефлексивно определяют метамодель. Статья изрядно загрузная, математиками писанная, но мне лично, в свое время, понять Smalltalk изрядно помогла.
А что касается определнения в Smalltalk чего-нить "не средствами языка", так тут весь вопрос в отношении к примитивам. Если метод реализованный примитивом считаем "Smalltalk`овским", то вообще все средствами языка сделано.
Здравствуйте, eao197, Вы писали:
E>Так ведь, Андрей, в этом и все дело. Я почему заинтересовался duck typing-ом? Потому, что в реальной жизни нет интерфейсов, нет наследования.
Интерфейсы есть. Наследования действительно нет. Но, по большому счету, в реальной жизни нет и тех классов, что присутствуют в программах. Объектная модель программы это всего лишь модель, причем заточенная и оптимизированная под решение конкретной задачи. И, зачастую, эта модель очень слабо похожа на то, что можно пощупать руками.
E> Тот же чемодан может играть как роль контейнера для багажа при переноске (тогда для него важна ручка), так и роль хранилища не нужных вещей (тогда не важно есть ли ручка),
В мире ООП это означает что конкретный класс может реализовывать любой набор интерфейсов.
E>ООП есть способ отражения реального мира в программный код.
Модели, придуманной архитектором, а не реального мира. А уж откуда взялась эта модель — дело десятое.
E> В статически типизированных языках поэтому нам приходится мыслить интерфейсами и их иерархией (наследованием). И если, скажем, нам нужна подставка, то мы описываем интерфейс подставки и требуем, чтобы классы наследовались от него
Реализовывали будет более точным.
E> (иначе их экземпляры нельзя будет использовать в качестве подставок). А это значит, что в нашей программе мы должны изначально предусматривать потребуются ли нам подставки вообще, а если потребуются, то что мы будем в их качестве использовать. И если мы в чем-то ошиблись (например, забыли унаследоваться в каком-то классе от интерфейса "подставка"), то производим рефакторинг. А если рефакторинг не возможен (скажем, приходится работать с классами из 3rd party библиотеки), то делать дополнительные обертки. А уж если нам потребуется совместить в одном классе совершенно разные интерфейсы (контейнер для багажа, хранилище не нужных вещей, подставка), то наследование класса от всех этих интерфейсов выглядит, прямо скажем, странно. Но хуже всего то, что эту иерархию приходится регулярно, как говорят у меня на родине, ператрахивать.
А альтернатива какая? Использовать чемодан в качестве подставки, даже если он не рассчитан на это? А если он от этого сломается? Ты пойми, я не зря постоянно оговариваюсь что ОО-модель оптимизированна под конкретную задачу. В отличие от real-чемодана, который можно заюзать как угодно, конкретная ОО-модель чемодана может быть рассчитана только под конкретный набор операций, и попытка использовать ее иначе в лучшем случае приведет к ошибкам.
E>Имхо, duck typing позволяет при проектировании и программировании придерживаться других правил: правил "похожести". Если какая-то сущность похожа на подставку, давайте будем использовать ее как подставку. Если похожа на хранилище ненужного хлама -- забъем ее под завязку. В этом, имхо, и разница между ООП и duck typing-ом. В ООП мы описываем похожести в виде интерфейсов и требуем их оформления и наследования. В duck typing-е мы просто находим похожести и используем их затем без наследования.
Если перевести на русский, то мы фактически отказываемся от контракта (кстати, более последовательным была бы возможность не сопоставления по имени, а возможность опционального задания карты отображения одних методов на другие). И вот тут начинается самое интересное — либо мы вынуждены изучать чемодан на предмет внутренней структуры и прикидывать, не развалится ли он когда мы на него телевизор поставим, либо поставить и посмотреть что произойдет.
E> Интерфейсы и там, и там существуют. Но подход к ним разный. Похожесть, например, в Ruby определяется не по наследованиию от чего-нибудь, а по наличию метода, реализующего нужную нам операцию.
Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
E> Поэтому, имхо, при проектировании в рамках duck typing-а мы просто-напросто опускаем такую часть, как построение иерархии наследования.
Здравствуйте, eao197, Вы писали:
V>>Просто иметь метод — недостаточно. Нужно, чтобы он ещё откуда-то вызывался. В простейшем случае — дать понять "клиенту", что у "сервера" есть такой метод. E>А зачем? Пусть вызывает то, что считает нужным. E>Если мы имеем дело с динамической типизацией, то получим исключение в run-time. E>Если со статической (как в C++ шаблонах), то нас остановит компилятор.
В run-time получать исключения совсем не хочется. Те, кто проворачивал достаточно большие проекты, не отсекающие такие ситуации до рантайма, знают, что это неиссякаемый источник чудовищного геморроя.
E>Не нужно ничего выдумывать. Если клиент предполагает, что он общается с уткой, то нужно ему сервера-утки и подсовывать. Только и всего.
Хорошо, когда это так, но иногда клиент может отрабатывать разных животных, но уток он хочет отрабатывать как-нибудь по-особенному. Например, вызывать у них не методы ЦыпЦып() или КисКис(), а именно УтиУтиУти().
E> E>А чем это duck typing-у противоречит?
Процитирую корневой постинг:
Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
У меня же есть отношение "is-a", нет отношения "like-a", полиморфизм есть, наследование можно не применять (оно просто рассматривается как необязательный частный случай множественной классификации), динамическая строгая типизация (с поддержкой мутации!) есть.
V>>Справедливый вопрос: а как добавлять специфические фичи к поведению утки, если реализующий его исходный код предлагается (или даже рекомендуется) не трогать? Да очень просто — реализуем эти фичи для сочетаний классов. Например, для сочетания (Утка, Дичь) реакцию на посылку сообщения "УтиУтиУти" можно сделать именно такой, какая характерна для дикой утки.
E>Приведи пример, пожалуйста, a?
// Как всегда, на вымышленном языке программирования
class Duck { // Утка
func УтиУтиУти(Somebody: AHuman);
}
class Alive { // Нечто живое, что может двигаться само
func Move(Direction: ADirection);
}
class Wild {} // нечто дикое
class Domestic {} // нечто домашнее
func (Duck, Alive)::УтиУтиУти(Somebody: AHuman) { // Сваливаем подальше
MakeSound("Кря-кря");
};
func (Duck, Wild, Alive)::УтиУтиУти(Somebody: AHuman) { // Сваливаем подальше
Move(CalcOppositeDirection(GetDirectionTo(AHuman)));
};
func (Duck, Domestic, Alive)::УтиУтиУти(Somebody: AHuman) { // Бежим к человеку. Может, пищу дадут...
Move(GetDirectionTo(AHuman));
};
// где-то в программе...
var
Duck1: Duck, // Просто утка
Duck2: (Duck, Alive), // Живая утка
Duck3: (Duck, Wild), // Дикая утка
Duck4: (Duck, Wild, Alive), // Дикая живая утка
Duck5: (Duck, Alive, Domestic); // Домашняя живая утка
Duck1.УтиУтиУти(Me); // Ничего не будет. "Кря-кря" не скажет и никуда не двинется.
Duck2.УтиУтиУти(Me); // Скажет "Кря-кря", но никуда не двинется.
Duck3.УтиУтиУти(Me); // Ничего не будет.
Duck4.УтиУтиУти(Me); // Скажет "Кря-кря" и убежит.
Duck5.УтиУтиУти(Me); // Скажет "Кря-кря" и прибежит.
classify Duck2 as Wild; // Смутировали живую утку Duck2. Сделали её дикой.
Duck2.УтиУтиУти(Me); // Теперь не только крякнет, но ещё и убежит.
При этом можно предположить, что классы Duck и Alive — библиотечные, а Wild и Domestic — это наши дополнительные фичи.
ANS>Smalltalk считается объектно-ориентированным, а должен был бы ориентированным на обмен сообщениями.
Тут надо добавить для ясности.
Термин объектно-ориентированный больше подходит для C++,ObjectPascal,Java,С# и т.п. чем для Smalltalk, Self,... Языки типа C++ не являются объектными, т.е. далеко не все (а точнее почти ничего) с чем приходится иметь дело не является first-class-object. Тем не менне эти языки имеют массу кострукций, которые ориентированны на построение объектных программ.
В Smalltalk все (ну, почти все) является first-class-object. В частности first-class-object являются класс, методы, сам факт посылки сообщения, стек, тело методов и т.д. По-этому такой язык стоит называть объектным, а термин объектно-ориентированный плохо описывает ключевые особенности. Alan Kay imho предложил назвать Smalltalk "ориентированным на обмен сообщениями" что бы подчеркнуть эти особоенности. Стоит так же отметить, что Smalltalk именно ориентирован на обмен сообщениями, точно так же как C++ ориентирован на объекты. Не все, что по объектной концепции, должно бы, в Smalltalk является, сообщением. Например, возврат (из метода), не является посылкой собщения вызывающему объектку. Так же выброс исключения это тоже не сообщение.
Здравствуйте, eao197, Вы писали:
E>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
E>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
Кстати, динамическая типизация тут не обязательна. В С++ возможно объявить шаблонную функцию, которая будет рассчитывать на наличие определенных методов у своих аргументов — и это будет то же самое (только эти требования нигде в коде явно не прописаны, что плохо). Например, в качестве итератора можно передать какой угодно объект, имеющий оператор ++ (и может быть + и -), а в качестве аллокатора соответственно передается любой класс, имеющий такой же набор методов.
Здравствуйте, eao197, Вы писали:
E>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
Неверная аналогия. Ручка у дипломата это скорее аналог интерфейса. А вот аналогом duck typing было бы, если бы алгоритм проверял не наличие ручки, а наличие чего то, что ручкой называется. Такой алгоритм при этом бы пытался точно так же унести дверь от туалета или дернуть стоп-кран в поезде.
Здравствуйте, 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++.
Здравствуйте, VladD2, Вы писали: S>>Однако, как я подозреваю, микрософтеры сделали стрим таким потому, что считают свойства CanXXX не неотъемлемо приданными на уровне класса, а изменчивыми за время жизни одного и того же стрима. Это — единственное оправдание подобной архитектуры.
VD>Думаю, что они это сделали по глупости. Точно так же как использовали базовый класс вместо интерфейса.
Думаю, что это они сделали очень грамотно, а не по глупости. Такие же фишки есть в IList, IDictionary (IsReadOnly, IsFixedSize), в PropertyInfo (CanRead, CanWrite) и т.д. На мой взгляд это единственно правильное решение, потому как ReadonlyCollection, FixedCollection, WritableProperty и прочее — просто чушь о ерунде. Так же как IFixedCollection. Или IChangeableCollection. Совершеннейшая ерундистика с прагматической точки зрения выносить в метаданные то, что можно определить свойством.
Dyoma,
> Исторически статическая типизаия использовалась как способ дать компилятору подскази для построения эффективного кода. Например, С++ компилятор, строит vtbl для класса размером не на все возможные (в системе) сообщения, а только на определенные в этом классе. В Smalltalk, например, такой оптимизации не делается. По этому если Smalltalk объект получает "непонятное" для него сообщение, то он кидает исключение, а C++ объект начинает вести себя непредсказуемым образом (что почти всегда приводит к GPF, а иногда к порче данных или странному поведению).
Можно пояснить мысль поподробнее относительно того, что ты понимаешь под передачей объекту C++ "непонятного" для него сообщения?
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, VladD2, Вы писали:
VD> [ skipped ]
VD>А может проще дать пользователю библиотеки просто функцию передать в качестве параметра? Будет в принципе тот же traits: VD>
VD>и никаких расширений языка не потребуется. VD>В общем, если интерфейс заранее известен, то просто ползуемся ООП. Если нет, то применяем функциональный подход. В итоге все красиво, быстро и без утиных историй.
По-моему, такой перенос выбора способа позиционирования потока с уровня классов, представляющих поток (File, Socket, Stream, whatever), на уровень отдельных вызовов (функции load_header_and_data) — это совсем даже не просто и не красиво, и вообще выглядит как хак.
Здравствуйте, 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, которые принципиально не могут реализовывать интерфейсы. Но это мера вынужденная, а не некий универсальный принцип.
Здравствуйте, Кодёнок, Вы писали:
Кё>Кстати, динамическая типизация тут не обязательна. В С++ возможно объявить шаблонную функцию, которая будет рассчитывать на наличие определенных методов у своих аргументов — и это будет то же самое (только эти требования нигде в коде явно не прописаны, что плохо). Например, в качестве итератора можно передать какой угодно объект, имеющий оператор ++ (и может быть + и -), а в качестве аллокатора соответственно передается любой класс, имеющий такой же набор методов.
Для решения подобных проблем в С++ предлагается добавить нечто вроде констрэйнов из Шарпа. В С++ их предлагается назвать Concepts. Тут недавно дали ссылку на презинтацию Саттера (из МС). Там этот вопрос подробно описан.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Dyoma, Вы писали:
E>>Есть такая штука: Duck typing. В языках, вроде Smalltalk, Python и Ruby она активно используется.
D>За Python и Ruby не скажу — дела с ними не имел, а Smalltalk — это одна сплошная "утиная история", подругому на нем писать нету синтаксических возможностей.
Забыл сюда добавить про движение ровно в противоположную сторону — StrongTalk. Он, к сожалению, так и остался академической разработкой, замятой компанией Sun. Идея заключалась в прикручивании к Smalltalk, системы типов, для целей верификации, подсказок IDE, и может еще чего.
StrongTalk расширяет синтаксис Smalltalk, добавляя опциональные ограничения на тип параметров. Ограничения могут иметь вид:
это параметр понимает такой-то протокол
понимает список протоколов — объединение протоколов.
понимает один из списка протоколов — пересечение.
Протокол — это так в Smalltalk называется понятие более известное как interface.
Здравствуйте, AndrewVK, Вы писали:
D>>От контратка прямо сразу никто не отказывается.
AVK>Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
Именно это (выделенное жирным) я и имел ввиду.
D>> Изменяется область применимости (scope) контракта, все методы названные одинакого должны выполнять один контракт.
AVK>Кому должны? Компилятор не в состоянии проконтроллировать это. А значит мы задачу по контролю семантической совместимости возлагаем на программиста.
Эта задача и так лежит на программисте, никакой компилятор не проверяет, что за бред написан в реализации такого-то метода. Проверка типов проверяет только есть ли у заявленного типа методы с таким-то именем, а уж для чего он использован — дело только программиста.
D>> Это похоже на перегрузку, если в одно интерфейсе (или классе) есть методы с одинаковым именем, но разными типами параметров, естественно ожидать, что делают эти методы, что-то очень похожее.
AVK>Совсем не похоже. Перегрузка сосредоточена внутри одного класса,
Я ж начал того, что scope контракта расширяется, теперь это дело не одного класса/интерфейса, а всего проекта.
AVK> так что если ты там что то напортачил, то сам дурак.
А как насчет пользователя этого метода? Особенно в ситуации, когда параметры перегруженных методов связаны по иерархии и явным приведением типа в месте вызова можно повлиять на то какой метод будет вызван?
AVK>А вот duck typing взаимодействует с чужим классом, который не обязан знать ничего о том как ты интерпретируешь конкретную сигнатуру. Говоря проще — в первом варианте инкапсуляция не нарушается, а во втором нарушается.
Во-первых еще раз о scope. А во-вторых, инкапсуляция это касается только того, как метод что-то делает, а не того что он делает.
D>>Тут вообще-то нужна дополнительная поддержка от языка. Т.к. контрактов на методы может быть много, то и названий должно быть много, и в языке должно быть естественно использование длинных имен методов (речь конечно не про названия f, g, и т.п.).
AVK>Это все подпорки. Гарантию несовпадения тебе дадут только гуиды.
Не совсем подпорки, все-таки это скорее решение проблемы, см. дальше.
AVK>Только вот с интерфейсами оно как то проще.
Согласен, что интерфейсы упрощают жизнь при реализации метода, но не надо забывать, что они же усложныют использование в непредусмотренных ситуациях.
D>> В Smalltalk эта проблема решена, имена методов естественным образом длинные, и проблем с пересечением названий для разных контрактов достаточно мало, что бы не мешать.
AVK>От это вряд ли.
Это не вряд ли, это факт.
AVK> Видишь ли, тут проблема не столько в том, что методы случайно совпали по сигнатуре, сколько в том что разные программисты могут интерпретировать одну и ту же сигнатуру по разному. Что бы этого не произошло нужно конкретные сигнатуры описывать формально, с указанием в комментариях/документации что под этой сигнатурой понимается. Чувствуешь к чему пришли?
А теперь о "подпорках". Вот я взял один из Smalltalk`ов (DolphinSmalltalk) и посмортел среднюю длинну имени метода (по библиотеке). Среднее арифметическое по различным именам, получилось больше 16 символов. Общее число (различных) имен методов почти 13000. Согласись 13000 — это очень большой словарный запас в него очень много понятий (в нашем случае контрактов) может поместиться.
Насчет коментрариев/документации — это опять же не зависит от наличия типов. Если ты вводишь какой-то новый контрак, ты должен дать другим возможность узнать что ты имел ввиду.
AVK>>>Вот нифига подобного. Нет никакой гарантии, что метод, называющийся определенным образом, выполняет именно нужную тебе операцию. В этом и проблема.
D>>Согласен в том смысле, что duck-typing стоит вводить в язык в версии 1.0, когда на нем еще ничего не написано, существенно закладываясь на типы.
AVK>Наоборот. Если проектировать с нуля, то duck typing совершенно бесполезен. Он оправдан только если мы не можем менять существующие наработки. Об этом я применительно к C# 3.0 в этом топике уже писал.
Я писал про версию языка 1.0, а не проекта. И имел ввиду, что если сразу разработчики библиотек не будут озабочены выбором уникальных имен для разных контрактов, то потом, смысла от duck-typing действительно будет существенно меньше.
Здравствуйте, 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>>Знаешь, я в этом не уверен. Впрочем это терминологический спор.
E>Так я, вроде, уже давно обозначил позицию: Re[4]: Утиные истории vs ООП?
E>ООП базируется на трех китах: инкапсуляция, наследование и полиморфизм.
E>Термин ООП я употребляю именно в этой интерпритации. Поэтому, если есть объект и есть методы (с полиморфизмом), но нет наследования (в принципе), то это уже не ООП.
Вообще-то ООП это объекты, обменивающиеся сообщениями. Отсюда следуют только инкапуляция и полиморфизм — потому как утверждается, что объекту сообщение послать можно, а как он на него будет реагировать это его дело. Наследование же появляется не во всех ООЯ, а только в (хотя бы динамически) типизированных. И даже когда появляется, то по разным причинам и с разными целями. В статически типизированных языках, наследование (как минимум интерфейсов) является необходимым требованием системы типов. В динимически — это не более чем способ реюза кода, т.е. без него можно обойтись, все решения будут возможны, только местами по-другому реюзать. Например код:
class Base {
void f() {...}
void g() {...}
}
class Derived {
void f() { super.f(); ...}
}
. Вопрос: какое в данном случае адекватнее?
Imho в Wikipedia определение от жизни, т.е. не важно о каких идеях думали отцы основатели, важно во что это на сегодня вылилось. А выливали ООП в жизнь, C++, ObjectPascal, а потом еще и Java с C#. Думаю, что как минимум 90% ООП на планете я этим списком покрыл. Отсюда и упоминание про остальные варианты в скобках.
В этой же дискусии ты идешь уже не от жизни (duck-typing не имеет такого распостранения), а от идей. Так что и определение надо брать идейное . Либо дискуссию стоило назвать "Утиные истории vs mainstream ООП".
Здравствуйте, eao197, Вы писали:
E>Совсем не точка. Мы с AndrewVK говорили об объектах, которые поддерживают несколько интерфейсов. В частности, чемодан поддерживает: "контейнер для ручной клади", "хранилище хлама", "подставка". Речь шла о том, что если чемодан как подставку использовать для не подходящей вещи, то он сломается, а мы поимеем проблемы. На что я возразил, что интерфейс "подставка", реализованный СтекляннымЖурнальнымСтоликом для того же самого телефизора так же не подойдет, получится исключения. Поэтому все compile-time проверки в этом случае идут лесом.
Прекратите аналогии с реальным миром. Ну, право же демагогия да и только.
Понимаш ли. С нормальными интерфейсами есть две проблемы. Одна психологическая. Другая структураная (сажем так).
Структурная вызвана тем, что иногда бывает, так что объект ссылку на который нужно передать в некий метод неудается туда передать в виду того, что его разработчик не предусмотрел у него нужный интерфейс. Скорее всего нужный интерфейс есть, но его проектировшик видел его несколько иначе нежели чем ользователь. Так же бывает, когда два проектировщика создали два, возможно даже одинаковых, интерфейса предназначенных для одного и того же. Если все это дело происходит в рамках одного проекта (а это очень частая ситауция, так как промышленные библиотеки и фрэймворки обычно проектируются тщательнее и раньше, а как раз "свои" более безолаберно), то можно просто сделать рефакторинг устраня тем самым откровенный косяк в проектировании. Целью рефакторинга будет удаление одно из интерфейсов и замена его на второй. Если же объкт все же находится в чужой бибилиотеке, то можно воспользваться одним из паттернов проектирования (не даром они для ООП и разрабатывались) и создать, к примеру, объект-обертку хранящую в себе ссылку на нужный объект и преобразующую его интерфейс в требуемый для фунции.
Психологическая связана с тем, что все эти рефакторинги и создание оберток геморойны, а обертки еще и скорость могут понизить. Причем забавно, что многих как раз рефакторин не так пугает, как гипотетические тормоза.
Так вот, боязнь рефакторинга при наличии средств вроде IDEA/ReSharper/VS2005 — это просто от серости и лени. Выигрыш тут несравнимо больший чем затрачиваемые усилия. Заключается он в грамотном дизайне приложения. А это, в свою очередь, дает простоту отладки и развития проекта.
С производительностью хуже. Но тут есть два "но". Превое — это то что она по большому счету должна решаться улучшеием компилятора. Так лишние объекты и виртуальные вызовы методов локальных объектов (объектов которые используются только в локалном контексте, например, в одном стэкфрэйме или ссылка на которые находится только в одной переменной и можно вычислить, что эта перменная не меняется в процессе работы приложения) могут попросту устраняться оптимизирующими компиляторами. У джита и преджита есть все шансы определить локальность перменных. Второе — только в довольно редких местах такие промежуточные объекты (и другие решения на базе паттернов проектирования) ощутимо влияют на производительность прилоежния. Их можно отловить с помощью профайлера и уже на конечных этапах разрабокти переписать оптимально журтувя при этом качеством кода. Зато общий дизайн приложения будет куда лучше.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, eao197, Вы писали:
E>>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
S>Duck typing, в принципе, не обязан работать динамически. Его можно было бы ввести, например, в constraintы generic. Т.е. вместо введения интерфейса ICountable { int Count { get; } } и заставления всех мыслимых параметров шаблона явно от него наследоваться при помощи S>
S>class SmartType<T>
S>where T: ICountable
S>{
S> public static int q(T param) { return t.Count;}
S>}
S>
S>мы бы вводили утиный интерфейс ICountable, который бы вынуждал выполнять биндинг на этапе инстанцирования дженерика. Вся информация в принципе есть; особой тяжести здесь нету — операция выполняется единожды для каждого типа. S>В отличие от, скажем, приведения произвольного object к нашему интерфейсу, которое не удастся сделать привычным ловким методом дотнета.
Здравствуйте, VladD2, Вы писали:
V>Тут недавно дали ссылку на презинтацию Саттера (из МС).
Обалдеть. С++ опять начинают перегружать терминами. По моему — там кто-то очень его не любит. И так язык загруженный — перегруженный, а сейчас вообще плохо будет.
Хотя все остальное очень понравилось. Особенно внедрение OpenMP на уровне языка Active Objects. (пока остальное не досмотрел). Вещь полезная, но боюсь еще больше усложнит программирование.
С уважением, Gleb.
ЗЫ А обсуждение ентого было? Чего-то я не нашел.
Здравствуйте, Dyoma, Вы писали:
D>Более того, типизация несколько мешает ОО. Чистое объектное видение мира подразумевает, что любой объект потенциально может отреагировать на любое сообщение. Или другими словами есть только объекты и объекты не различаются ничем, кроме своего поведения. Например, к каждому человеку можно подойти с вопросом "Сколько ты будешь делать?" с параметром ТЗ. На что можно получить в ответ "N дней", "Чего???" или по зубам . Но коллапса с миром не случит в любом случае.
Вот здесь уже противоречие. С одной стороны "объекты не различаются ничем, кроме своего поведения", с другой походить ты хочешь к "каждому человеку", а не, к примеру, к столбу. Ведь у столба ты вряд ли сможешь спросить что либо, и уж точно не сможешь дать ему по зубам. Так что класс объекта определяет его интерфейс. И если мы заранее знаем класс объекта, то можем не далать бессмысленных действий.
Например, если мы заблудились в лесу, то мы не будем подходить к каждому дереву и спрашивать "как выйти к станции?", за то мы будем подходить с подобным вопросом к каждому всречному человеку.
D>"Утиные истории" появились задолго до ОО, например тот же Lisp.
+1
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, eao197, Вы писали:
VD>>Ёлы, плалы. Ну, когда же ты наконце научишся приводить примры на 2-4 строчки кода? Зачем ты снова залудил гору кода, ну, не будет в нее никто вникать. Не надейся, что сможешь стать вторым Толстым.
E>Чтобы показать, сколько нужно генерить кода в C++ чтобы получить тот же эффект, что и в двух строчках Ruby или Python-а.
Ты оставь такие показы тем кто про бусты рассказывает. Глядишь они тоже в пару строк уложатся.
Пойми, я уже не в первый раз вижу у тебя примеры которые даже читать не хочется. А потом ты спрашиваешь почему на них никто не реагирует. Вот именно по тому. Нужно учиться формулировать задачи так, чтобы их можно было понять не напрягая все иуственные усилия. Ведь сами задачи то не сложные. Ты лучше чуть больше на естественном языке напиши.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, eao197, Вы писали:
ЗХ>>Наиболее изящным решением на C++ было бы — либо А: ЗХ>>
ЗХ>>template<typename T> seek(T,int);
ЗХ>>template<> seek<File>(File _f, int _bytes);
ЗХ>>template<> seek<Soket>(Soket _s, int _bytes);
ЗХ>>
E>А тебе не кажется, что эта специализация, фактически, эквивалентна приведенному мной выше skip_data?
Казалось бы. Ан нет.
Твой вариант — при появлении нового типа потока, требует модификации функции skip_data (которая может быть и недоступна).
Мой вариант — при появлении нового типа потока создаем еще одну (отдельную и независимую специализацию).
Второе отличие — очередное проявление "статика vs. динамика". В моем варианте, если неизвестно, как обрабатывать новый тип потока (нет специализации seek) — мне моментально ругнется компилятор. В твоем — если функция skip_data не учла новый тип потока — ругнется рантайм "когда-нибудь" — может быть, через 5 лет использоания
Здравствуйте, VladD2, Вы писали:
VD>Отличия с Явой тут нет. Явские дженерики тоже рантаймная сущьность. Просто они обрабатываются за счет полиморфизма.
Влад что ты имеешь ввиду под "рантаймная сущьность"? Явские дженерики с рантаймом никак не связаны. Вообще.
Здравствуйте, Cyberax, Вы писали:
C>Нечто такое собираются добавить в следующий Стандарт С++ под названием C>"Concepts".
Хорошо бы , но к сожалению в целях совместимости , как всегда оставят прежние шаблоны .
C>Нынешние шаблоны были спроектированы в 93 году Так что вполне C>понятно, что они не получились абсолютно идеальными.
Да уж , тогда явно разработчики мало задумывались о таких вещах как кривая обучения , читабельность , отладка и т.д. Явно внимание было сфокусированно на эффективности "code reuse" и т.д.
Здравствуйте, minorlogic, Вы писали:
M>Здравствуйте, Cyberax, Вы писали:
C>>Нечто такое собираются добавить в следующий Стандарт С++ под названием C>>"Concepts". M>Хорошо бы , но к сожалению в целях совместимости , как всегда оставят прежние шаблоны .
Применить то можно , но во первых совсем не изящно. А во вторых можно вообще програмировать красиво , без ошибок и т.д. Хороший язык должен поощрять хороший стиль и запрещать плохой.
Ну и сам посмотри трезвым взглядом сколько кода надо писать , который не несет ФУНКЦИОНАЛЬНОСИ. C++ мощный язык , тем что позволяет писать такие вещи , или например сборщики мусора. Но недостатков у него тоже хватает ... даже через чур ..
Здравствуйте, eao197, Вы писали:
E>Нет, я же специально сказал: "если эта штука похожа на чемодан и имеет ручку". Стоп-кран бы здесь не подошел.
Вот тем и отличается метод интерфейса от duck typing. Он говорит что это не просто ручка, а ручка для переноски, поскольку этот метод является частью интерфейса Переносимый. Более того, мы можем приспособить к чемодану две ручки, одну для переноски, а другую для просушивания на веревке, и, благодаря тому что ручки принадлежат разным интерфейсам, быть уверенным что переносить его будут только за одну ручку, а сушить на другой. В случае же duck typing нам остается только надеятся, что название метода на 100% отражает его предназначение.
Здравствуйте, AndrewVK, Вы писали:
D>>От контратка прямо сразу никто не отказывается.
AVK>Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
Да нет, как раз в том и суть duck typing, что это уже и не ОО.
Поэтому оценивать duck typing с точки зрения ОО не совсем корректно. Так же и ООП с точки зрения duck typing.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
AVK>>Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
E>Да нет, как раз в том и суть duck typing, что это уже и не ОО.
Контракт это тоже не обязательно ОО.
E>Поэтому оценивать duck typing с точки зрения ОО не совсем корректно.
Зачем тогда вы его описываете с точки зрения ОО? И что тогда за тип имеется ввиду (duck typing)?
D>> Вопрос: какое в данном случае адекватнее?
E>Действительно, какое?
Ну и программирование и дизайн на Smalltalk всеравно объектно-ориентированные. Вид программирования определяется все-таки патернами, и способом декомпозиции задачи. Патерны одни и теже, баланс несколько между ними разный, ну так он и между C++ — java разный. А язык — да, язык обозвали не совсем правильно (я там
Здравствуйте, eao197, Вы писали:
E>Совсем не точка. Мы с AndrewVK говорили об объектах, которые поддерживают несколько интерфейсов. В частности, чемодан поддерживает: "контейнер для ручной клади", "хранилище хлама", "подставка".
Нет. Речь шла о duck vs normal тайпинге. E>Речь шла о том, что если чемодан как подставку использовать для не подходящей вещи, то он сломается, а мы поимеем проблемы. На что я возразил, что интерфейс "подставка", реализованный СтекляннымЖурнальнымСтоликом для того же самого телефизора так же не подойдет, получится исключения. Поэтому все compile-time проверки в этом случае идут лесом.
Не идут они никаким лесом. Потому, что стратегия реакции на постановку чего-либо является частью семантики интерфейса подставки. У нее есть детерминированный способ отреагировать на телевизор — выкинуть ArgumentException или InvalidOperationException. Это в корне отличается от ситуации, когда мы просто передаем наш телевизор в случайно попавшийся метод "поставить". А этот метод, в свою очередь, может попытаться выполнить над поставленной на него вещью какую-то не ту операцию. И т.д.
E>Ok. E>Интересная, однако, тенденция. Первоначально ООП вообще не подразумевало наследования (Re[3]: Снова о типизации :)
), затем появились постулаты инкапсуляция/полиморфизм/наследование, теперь сюда же еще и интерфейсы добавляются. E> Имхо, если понятие такое аморфное и видоизменяется со временем, то это отнюдь не строгое понятие. А может и не понятие вовсе.
Я тебе очень советую почитать научную литературу на эту тему. Ну, к примеру, теорию классификации на jot.fm E>Все перечисленные тобой проблемы постоянно проявляются и в программах со статической типизацией. В которых совершенно нет duck typing-а.
Ну и что? Давай теперь выкинем на помойку средства по предотвращению таких ошибок, и запользуем средства по их внесению.
E>Все это было бы так, если бы программы на языках со статической и сильной типизацией работали бы стабильно и без ошибок. Но ошибки есть. Как те, что проявляются сразу в момент возникновения каких-то проблем, так и наведенные, проявлюящиеся спустя длительное время.
E>А в том, что ты говоришь, есть изрядная доля веры в компилятор и статическую типизацию. На первый взгляд кажется, что статическая типизация -- это панацея от большого класса ошибок. Но, с другой стороны, статическая типизация требует от разработчика больших усилий при проектировании и реализации.
E>А что в итоге: ошибки в ПО все равно есть. И для потребителя программ это главное. Ему без разницы, статическая там типизация или динамическая. Раз программа упала, значит недотестировали. Или условия задачи не поняли. Или реализация со всеми ограничениями наследования/интерфейсов оказалась настолько запутанной, что в поведении программы вообще мало кто разбирается.
Я не вижу никаких причин для программы с дак-тайпингом стать сильно проще.
E>Статическая типизации и "современный" ООП применяются повсемесно. Но повышения надежности программ и сокращения сроков разработки не происходит.
Происходит и еще как.
Основные причины падения и неустойчивостей происходят от:
— использования унаследованных технологий (покажите мне хоть один buffer overrun в управляемом приложении! А в неуправляемых их ежегодно отлавливают). E>Duck typing применяется гораздо реже, многие из моих оппонетнов в данном топике, вероятно, вообще его не применяют. Но при этом пытаются доказать, что duck typing менее надежен, чем статическая типизация и интерфейсы. Может быть вы и правы.
Я не понимаю, как убирание средства контроля правильности программы может помочь контролю правильности программы.
E>Хотя, с другой стороны, может быть duck typing позволяет тратить меньше времени на проектирование/разработку, а освободившееся время посвящать более тчательному и всестороннему тестированию? Может быть здесь сработает тот же принцип, что и при программировании на динамических языках: ошибок гораздо меньше, чем предполагаешь в начале. А из-за чего -- точно не понятно. Может быть потому, что больше тестируешь. Может быть потому, что код получается компактным. Может быть потому, что меньшим количеством сущностей оперировать нужно. Много мелких факторов, которые складываются и дают совершенно неожиданный для тебя результат.
Может быть. Но очень уж сомнительно.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
eao197,
> AVK>Т.е. то, что реальный софт пишут команды, ты в своей оценке не учитываешь? > > Блин, да я же не агитирую всех за тотальное использование duck typing-а. Я хочу понять границы его применимости. > Командная разработка, действительно, вряд ли выиграет от использования duck typing-а. Это очень серьезный минус для duck typing-а.
Зависит от конкретной команды и конкретной задачи. Structural/Latent/Duck typing -- инструмент, вполне полезный и при командной разработке. Использование Smalltalk, Lisp и шаблонов C++ вполне это подтверждает.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Program.cs(8,15): error CS0117: 'A' does not contain a definition for 'Add'
Program.cs(8,15): error CS0117: 'A' does not contain a definition for 'Add'
Program.cs(8,15): error CS0117: 'A' does not contain a definition for 'Add'
Done building project "LINQConsoleApplication1.csproj" -- FAILED.
========== Build: 0 succeeded or up-to-date, 1 failed, 0 skipped ==========
В общем, явно сырая альфа. И не туда, и не сюда. Без интерфйса жить не хотя, но интерфейс тоже не понимют.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Павел Кузнецов, Вы писали:
>> Наверно нечто вроде: >>
>> int i = 0;
>> double* pd = (double*)&i;
>> *pd = 1.3;
>>
ПК>Что, очевидно, таковым не является, и, более того, запрещено спецификациями языков Си и Си++ (хотя среда исполнения и не обязана детектировать нарушения).
А кто такой "хотя среда исполнения"?
----------------------------------------------------------------
— Дети! Запешите предложине — "В углу сребет мышь.". Все понятно, дети?
— Да, Марья Ивановна... только... ээаа... А кто такой "Вуглускр"?!
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Кодёнок, Вы писали:
E>>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
E>>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
Кё>Кстати, динамическая типизация тут не обязательна. В С++ возможно объявить шаблонную функцию, которая будет рассчитывать на наличие определенных методов у своих аргументов — и это будет то же самое (только эти требования нигде в коде явно не прописаны, что плохо). Например, в качестве итератора можно передать какой угодно объект, имеющий оператор ++ (и может быть + и -), а в качестве аллокатора соответственно передается любой класс, имеющий такой же набор методов.
Есть небольшое отличие: динамические языки проще адаптировать к некоторым условиям. Например, нужно прочитать из потока заголовок, определить, сколько байт нужно пропустить, а затем прочитать еще несколько байт. Если потоком является файл, то можно просто сделать seek, а если потоком является сокет, то пропускаемые данные нужно читать:
def load_header_and_data( stream )
h = load_header( stream )
if stream.respond_to( "seek" )
stream.seek( h.get_length() )
else
stream.read( h.get_length() )
end
load_data( stream )
end
E>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
Так вроде Duck typing обобщенное программирование в чистом виде, а это довольно старая парадигма
Здравствуйте, FR, Вы писали:
E>>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
FR>Так вроде Duck typing обобщенное программирование в чистом виде, а это довольно старая парадигма
На момент появления C++ ООП так же было старой парадигмой
In computer science, generics is a technique that allows one value to take different datatypes (so-called polymorphism) as long as certain contracts such as subtypes and signature are kept. The programming style emphasizing use of this technique is called generic programming.
Duck typing -- это обобщенное программирование в терминах signatures. А обобщенное программирование в C#/Java -- в терминах subtypes, т.е. в рамках традиционного ООП с наследованием.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Duck typing -- это обобщенное программирование в терминах signatures. А обобщенное программирование в C#/Java -- в терминах subtypes, т.е. в рамках традиционного ООП с наследованием.
) E>>Чесно говоря, подобный duck typing в C++ меня бы забабахал сразу же.
Кё>Ну это недостаток лично С++ (дело все лишь в добавлении одной фичи, аналогичной typeof или sizeof), а не статически типизированных языков.
Ok. А как бы это выглядело со статически типизированным C# и его generic-ами? Можно ли там будет добавить такое ключевое слово, которое можно будет применить к параметру шаблона?
Здравствуйте, eao197, Вы писали:
E>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами? Можно ли там будет добавить такое ключевое слово, которое можно будет применить к параметру шаблона?
has_method должен быть инструкцией компилятору, т.е. полностью выполняться во время компиляции, и быть замененным на результат. Как sizeof в C++. Тогда has_method может принимать идентификаторы или строковые литералы (has_method(stream, "Seek")), т.к. разницы особой нет (разве что строковые литералы можно "склеить" с другими и потенциально ввести еще инструкции для разбора строк во время компиляции, т.е. возможностей с ними больше).
Единственное условие — чтобы код, который никогда не выполнится (if (false) ...), не проверялся на корректность. Более сложный вариант — чтобы проверялся весь код, кроме обращений к методам, которые пытались найти с помощью has_method. Т.е. если метода Seek нет, но компилятор видит, что программист использовал has_method, чтобы его найти, он не проверяет весь стейтмент. Что лучше, не знаю.
Здравствуйте, Кодёнок, Вы писали:
E>>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами? Можно ли там будет добавить такое ключевое слово, которое можно будет применить к параметру шаблона?
Кё>Теоретически — нет проблем, так же как и в С++.
<...код поскипан...> Кё>has_method должен быть инструкцией компилятору, т.е. полностью выполняться во время компиляции, и быть замененным на результат. Как sizeof в C++. Тогда has_method может принимать идентификаторы или строковые литералы (has_method(stream, "Seek")), т.к. разницы особой нет (разве что строковые литералы можно "склеить" с другими и потенциально ввести еще инструкции для разбора строк во время компиляции, т.е. возможностей с ними больше).
, то оказывается, что в отличии от generic-ов Java, generic-и C# предназначены для инстанциирования в run-time (не compile-time). Поэтому компилятор на этапе компиляции приведенного тобой кода не сможет понять, какая сигнатура будет у метода Seek.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали: E>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами?
С дженериками — никак. Потому, что такая базовая вещь, как поток, не должна вводиться через дженерик. Вот так выглядит код на реальном шарпе:
public void LoadHeaderAndData(Stream stream)
{
Header h = LoadHeader(stream);
if(stream.CanSeek())
stream.Seek(h.GetSkipLength());
else
{
byte[] buffer = new byte[h.GetSkipLength()];
stream.Read(buffer, 0, h.GetSkipLength());
}
LoadData(stream);
}
... << RSDN@Home 1.1.4 stable rev. 510>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, eao197, Вы писали: E>>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами? S>С дженериками — никак. Потому, что такая базовая вещь, как поток, не должна вводиться через дженерик.
Это почему? Где-то потоком будет файл. Где-то строка. Где-то сокет.
Или вокруг этого какие-то обертки, реализующие некоторый интерфейс строить придется?
S> Вот так выглядит код на реальном шарпе:
S>
Здравствуйте, Dyoma, Вы писали:
E>>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
D>Типизация и ОО — вещи абсолютно ортогональные. ОО — способ смотреть на мир. С точки зрения ОО весь мир — объекты, с все взаимодействия между объектами — посылка сообщений. О типизации ОО вообще ничего не говорит.
А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
D>С этой мыслью, я не спорю, но она imho к вопросу отношния не имеет. Скорее к вопросу, какие парадигмы есть возможность сделать mainstream.
Так и я о том, что не станет ли duck typing мейнстримом.
E>>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
D>"Утиные истории" появились задолго до ОО, например тот же Lisp.
Да, но особенности Lisp-а и Smalltalk-а не позволили им набрать большого количества сторонников. А вот Python и Ruby популярность набирают.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Это почему? Где-то потоком будет файл. Где-то строка. Где-то сокет. E>Или вокруг этого какие-то обертки, реализующие некоторый интерфейс строить придется?
Совершенно верно. Потому что требовать от, скажем, строки поддержки метода Read() как-то странно.
В стандартную библиотеку FCL входят и NetworkStream и FileStream.
StringStream нету, потому что поток — это байты, а строка — это символы. Аналогом является MemoryStream.
Для работы с текстовыми данными есть отдельные классы TextReader и TextWriter.
E>CanSeek -- это метод интерфейса Stream? Абстрактного класса Stream.
... << RSDN@Home 1.1.4 stable rev. 510>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, eao197, Вы писали:
E>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
Ну не используется is-a в ООП. Is-a появляется вместе с типами (классами). Вот рассмотрим два варианта: объект используем и объект имплементим.
Когда искользуем, то посылаем ему сообщение, и тут совершенно не важно is-чего-он-там. Важно, что он объект, и послать можно. Тут скорее речь о responds-to. Вот это действительно интересно. В статически типизированых языках тип объекта дает ответ на этот вопрос. В димамически — может дать ответ класс, но не обязательно. В Smalltalk класс может дать только положительный ответ, а отрицательный — не может (см. _vovin вчера подробно про doesNotUnderstand: написал). Is-a это важно компилятору, например, C++. Если объект типа MyClass то сообщени foo() посылается через 5ю запись в vtbl.
Теперь про имплементим. Тут надо написать как объект реагирует на сообщения. Возьмем Self или JavaScript. Никакого is-a. Хватаешь объект и давай ему добавлять/удалять поведение. Опять приходим к response-to.
Но если используем типы (классы), то появляется возможность добавить поведение отнаследовав его от другого класса. Так же тут появляется необходимость в объявлении поддерживаемых наборов сообщений (протоколов, интерфейсов, или как это еще кто назвал). А нужно это только для системы типов, что бы с ее помощью проверить код, или сделать какие-то выводы, но сама идея ОО тут ни при чем.
В твоем примере, что бы таскать багаж типы действительно не нужны. И грузчику ты действительно скажешь "беги за ручку и неси туда". А вот что бы такую процедуру записать на статически типизированном языке тебе понадобится тип "ШтукаСРучкой".
D>>С этой мыслью, я не спорю, но она imho к вопросу отношния не имеет. Скорее к вопросу, какие парадигмы есть возможность сделать mainstream.
E>Так и я о том, что не станет ли duck typing мейнстримом.
Ну от чего же? А VB, особенно VBA, VBscript? Опять же JavaScript? А если еще C#3.0 выйдет?
D>>"Утиные истории" появились задолго до ОО, например тот же Lisp.
E>Да, но особенности Lisp-а и Smalltalk-а не позволили им набрать большого количества сторонников. А вот Python и Ruby популярность набирают.
Я про Lisp написал, как еще один аргумент, что ОО — концепция ортогональная.
Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>Здравствуйте, eao197, Вы писали:
E>>Есть небольшое отличие: динамические языки проще адаптировать к некоторым условиям. Например, нужно прочитать из потока заголовок, определить, сколько байт нужно пропустить, а затем прочитать еще несколько байт. Если потоком является файл, то можно просто сделать seek, а если потоком является сокет, то пропускаемые данные нужно читать: E>>
ЗХ>Для читателя кода — stream.respond_to( "seek" ) — выглядит как грязный хак. ЗХ>Код должен (в идеале, естественно) читаться как постановка задачи.
Ок. Переписываем:
def load_header_and_data( stream )
h = load_header( stream )
skip_data( stream, h.get_length() )
load_data( stream )
end
а код skip_data прячем:
def skip_data( stream, size )
if stream.respond_to( "seek" )
stream.seek( size )
else
stream.read( size )
end
end
Код load_header_and_data стал понятнее. А хак, хоть и спрятанный, остался.
Но ведь на самом деле в C++ мы делаем то же самое посредством специализации шаблонов. Да еще и больше по объему получаем.
ЗХ>Однако же. Ты реализовывал не свою постановку задачи "Если потоком является файл, то можно просто сделать seek, а если потоком является сокет", а другую постановку задачи "если у объекта, каким бы он ни был, есть метод seek, то..."
Однако же, это на первый взгляд так, если смотреть с позиции "is-a". Тогда действительно, файлом для нас будет что-то, что есть File (производное от File), а сокетом -- что-то производное от Socket.
Если же посмотреть со стороны duck types, то разница между Socket-ом и File в том, что в File можно переставить позицию, а в Socket-е -- нет. И тогда мы получаем возможность за "сущность, у которой есть seek" спрятать не только File (то, о чем мы думали вначале), но и какой-нибудь MemoryBlock, или SmartCardReader. Соответственно за сокет мы можем выдать не только Socket, но и UIDGenerator или IOController.
ЗХ>Наиболее изящным решением на C++ было бы — либо А: ЗХ>
ЗХ>template<typename T> seek(T,int);
ЗХ>template<> seek<File>(File _f, int _bytes);
ЗХ>template<> seek<Soket>(Soket _s, int _bytes);
ЗХ>
А тебе не кажется, что эта специализация, фактически, эквивалентна приведенному мной выше skip_data?
ЗХ>либо Б: ЗХ>
А вот это означает, что при проектировании классов File и Socket мы должны были предусмотреть возможность операции skip. Но ведь всего не предусмотришь. А попытка предусмотреть приводит к жирным интерфейсам.
ЗХ>А твое решение (как на Руби, так и на С++) совершенно нерасширяемо — завтра у тебя появится еще один тип потока, у которого надо будет вызвать вообще функцию, скажем, move, и что? ЗХ>Бррр!
А какой другой вариант? Требовать, чтобы все классы наследовались от одного интерфейса (абстрактного базового класса)? Не то ли это развитие событий, против которого возражал McSeem2?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Dyoma, Вы писали:
D>Здравствуйте, eao197, Вы писали:
E>>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
D>Ну не используется is-a в ООП. Is-a появляется вместе с типами (классами). Вот рассмотрим два варианта: объект используем и объект имплементим.
Ok. Тогда так. ООП базируется на трех китах: инкапсуляция, наследование и полиморфизм.
Если, по-твоему, наследование -- это не "is-a", то что такое наследование?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, VladD2, Вы писали:
VD>Ёлы, плалы. Ну, когда же ты наконце научишся приводить примры на 2-4 строчки кода? Зачем ты снова залудил гору кода, ну, не будет в нее никто вникать. Не надейся, что сможешь стать вторым Толстым.
Чтобы показать, сколько нужно генерить кода в C++ чтобы получить тот же эффект, что и в двух строчках Ruby или Python-а.
Не нравится, не читай
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, Кодёнок, Вы писали:
E>>>На C++ных шаблонах пришлось бы делать какие-то фокусы со специализацией (плагиат отсюда: Кто хотел определения наличия функции-члена?
) E>>>Чесно говоря, подобный duck typing в C++ меня бы забабахал сразу же.
Кё>>Ну это недостаток лично С++ (дело все лишь в добавлении одной фичи, аналогичной typeof или sizeof), а не статически типизированных языков.
E>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами? Можно ли там будет добавить такое ключевое слово, которое можно будет применить к параметру шаблона? E>
E>или же там все это нужно через рефлекшен делать (только толку тогда от такой статической типизации)?
Ты не поверишь! В C# для описания интерфейсов классов используются... интерфейсы!
В дженериках введена возможность описывать требования к параметрам типов с помощью ключевого слова where. Выгядит это так:
interface IWorker
{
void DoWork();
}
class A
{
// Статически описанное ограничение. Код не скомпилируется если тип
// используемый как аргумент типа данной функции не будет реализовывать
// workerpublic static void ConstrainedTest<T>(T value)
where T: IWorker
{
value.DoWork();
}
// Динамический запрос интерфейса.public static void DynamicTest<T>(T value)
{
IWorker worker = value as IWorker;
if (worker != null)
worker.DoWork(); // этот код выполнится только если переданный объект реализует IWorker
}
}
Что касается примера с потоками, то класс Stream в дотнете содержит свойство CanSeek позволяющее узнать можно ли этот поток позиционировать.
В С++ же предлагается похожая на констрэйны идея под названием Concepts
, то оказывается, что в отличии от generic-ов Java, generic-и C# предназначены для инстанциирования в run-time (не compile-time). Поэтому компилятор на этапе компиляции приведенного тобой кода не сможет понять, какая сигнатура будет у метода Seek.
Отличия с Явой тут нет. Явские дженерики тоже рантаймная сущьность. Просто они обрабатываются за счет полиморфизма.
А описание ограничений на параметры типа задается по разному. В Шарпе для этого служат констрэйны, а в Яве маски.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, eao197, Вы писали: E>>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами? S>С дженериками — никак. Потому, что такая базовая вещь, как поток, не должна вводиться через дженерик. Вот так выглядит код на реальном шарпе:
S>
CanSeek — это свойство. И дело тут даже не в базовости потока, а втом, что когда их разрабатывали дженериков еще небло. А так можно было бы в принципе сделать иерархию потоков. Однко нафига тут дженерики не ясно. Я бы это дело реализовал бы вот так:
interface ISequentialInputStream : IDisposable
{
int Read(byte[] buffer, int offset, int count);
int ReadByte();
}
interface ISequentialOutputStream : IDisposable
{
void Write(byte[] buffer, int offset, int count);
void WriteByte(byte value);
}
interface IRandomStream
{
long Seek(long offset, SeekOrigin origin);
long Length { get; }
long Position { get; set; }
}
interface IRandomInputStream : ISequentialInputStream, IRandomStream
{
}
interface IRandomOutputStream : ISequentialOutputStream, IRandomStream
{
void SetLength(long value);
}
И тогда все проверки можно было бы делать с помощью операторов приведения типов и is.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, Dyoma, Вы писали:
E>>>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
D>>Типизация и ОО — вещи абсолютно ортогональные. ОО — способ смотреть на мир. С точки зрения ОО весь мир — объекты, с все взаимодействия между объектами — посылка сообщений. О типизации ОО вообще ничего не говорит.
E>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "is-a" для определения взаимосвязи между сущностями. Отсюда и необходимость выстраивать иерархии наследования. А если брать duck typing, то наследование здесь вообще пофигу, нет никакого "is-a". Если "like-a". Т.е., если эта штука похожа на чемодан и имеет ручку, то я возьму его и отнесу. И мне не нужно при проектировании плодить сущности "багаж"-"ручная кладь"-"чего-то-там". И если я на самом деле имею дело с портфелем или дипломатом, то мне без разницы, состоит ли он в каких-то родственных оношениях с чемоданом или авоськой.
Ну, вот С++ со своими шаблонами сегодня живет по принципу "like-a". Что приводит к забавнейшему поведению компилятора. На такой вот простой код:
int i = 0;
int j = 2;
std::sort(i, j);
Он реагирует выдачей следующего сообщения об ошибке:
e:\vs\vs2005\vc\include\xutility(1059) : error C2665: 'std::_Debug_range2' : none of the 2 overloads could convert all the argument types
e:\vs\vs2005\vc\include\xutility(1038): could be 'void std::_Debug_range2<_InIt>(_InIt,_InIt,const wchar_t *,unsigned int,std::input_iterator_tag)'
with
[
_InIt=int
]
e:\vs\vs2005\vc\include\xutility(1044): or 'void std::_Debug_range2<_InIt>(_RanIt,_RanIt,const wchar_t *,unsigned int,std::random_access_iterator_tag)'
with
[
_InIt=int,
_RanIt=int
]
while trying to match the argument list '(int, int, const wchar_t *, unsigned int, std::iterator_traits<int>::iterator_category)'
e:\vs\vs2005\vc\include\algorithm(2138) : see reference to function template instantiation 'void std::_Debug_range<_RanIt>(_InIt,_InIt,const wchar_t *,unsigned int)' being compiled
with
[
_RanIt=int,
_InIt=int
]
e:\myprojects\tests\strcpy\strcpy\strcpy.cpp(32) : see reference to function template instantiation 'void std::sort<int>(_RanIt,_RanIt)' being compiled
with
[
_RanIt=int
]
Причем указывает он при этом не на строку с ошибкой, а на строку 1059 в файле e:\vs\vs2005\vc\include\xutility:
template<class _InIt> inline
void __CLRCALL_OR_CDECL _Debug_range(_InIt _First, _InIt _Last, const wchar_t *_File, unsigned int _Line)
{ // test iterator pair for valid range
_Debug_range2(_First, _Last, _File, _Line, _Iter_cat(_First));
}
Позовите сейчакс сюда артодоксов С++ и они вам расскажут, что это совершенно нормально или о том, что можно найти компилятор который даст чуть более разумное сообщение.
Между тем проблема лекго решается путем введения интерфейса.
E>Да, но особенности Lisp-а и Smalltalk-а не позволили им набрать большого количества сторонников. А вот Python и Ruby популярность набирают.
Вся история программирования — это борьба с подходами вроде утиной типизации. А ты призывашь обратно в каменный век.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, GlebZ, Вы писали:
GZ>Здравствуйте, VladD2, Вы писали:
V>>Тут недавно дали ссылку на презинтацию Саттера (из МС). GZ>Обалдеть. С++ опять начинают перегружать терминами. По моему — там кто-то очень его не любит. И так язык загруженный — перегруженный, а сейчас вообще плохо будет.
Не, эти расширения выпучены. Большое заблцждение, что С++ имеет сложный синтаксис. С++ имеет сложную семантику. И такие усложения синтаксиса позволят значительно упростить семантику.
Понимаеш ли... на практике сложность С++ для программиста проистекает не из того, что код прочесть очень сложно. А из того, что порой мельчайшее непонимани или ошибка приводит к тупиковой ситуации из которой только прожженный в боях гуру сможет выйти с честью. Вот тут
Здравствуйте, VladD2, Вы писали:
VD>CanSeek — это свойство. И дело тут даже не в базовости потока, а втом, что когда их разрабатывали дженериков еще небло. А так можно было бы в принципе сделать иерархию потоков.
Вот с этого места я вообще не понимаю. VD>Однко нафига тут дженерики не ясно.
А вот с этого — уже понимаю
Дженеризация тут как козе баян. И так все прекрасно работает. Я, честно говоря, хоть убей не могу принять этой концепции алгоритма сериализации "во что угодно". Ну ладно, в строку я пишу, в файл, в сокет... А ну как кто-то меня интом параметризовать вздумает? Где смысл, где логика?
VD>Я бы это дело реализовал бы вот так: VD>
VD>interface ISequentialInputStream : IDisposable
VD>{
VD> int Read(byte[] buffer, int offset, int count);
VD> int ReadByte();
VD>}
VD>interface ISequentialOutputStream : IDisposable
VD>{
VD> void Write(byte[] buffer, int offset, int count);
VD> void WriteByte(byte value);
VD>}
VD>interface IRandomStream
VD>{
VD> long Seek(long offset, SeekOrigin origin);
VD> long Length { get; }
VD> long Position { get; set; }
VD>}
VD>interface IRandomInputStream : ISequentialInputStream, IRandomStream
VD>{
VD>}
VD>interface IRandomOutputStream : ISequentialOutputStream, IRandomStream
VD>{
VD> void SetLength(long value);
VD>}
VD>
VD>И тогда все проверки можно было бы делать с помощью операторов приведения типов и is.
Ну, я все едино даункасты не люблю. Но сама идея, имхо, в целом правильная. Тогда можно строить алгоритмы, которые честно декларируют свои требования в виде типов параметров, а не в виде документации. Вместо
public static Copy(Stream source, Stream target)
{
byte[] buffer = new byte[16384];
int read = 0;
while((read = source.Read(buffer, 0, buffer.Length)>0)
target.Write(buffer, 0, read);
}
который с треском вылетит, если ему дали не CanRead/CanWrite потоки, можно было бы делать
public static (ISequentialReadStream source, ISequentialWriteStream target){}
И тут уж ты не ошибешься.
Однако, как я подозреваю, микрософтеры сделали стрим таким потому, что считают свойства CanXXX не неотъемлемо приданными на уровне класса, а изменчивыми за время жизни одного и того же стрима. Это — единственное оправдание подобной архитектуры.
... << RSDN@Home 1.1.4 stable rev. 510>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>[/c#] S>мы бы вводили утиный интерфейс ICountable, который бы вынуждал выполнять биндинг на этапе инстанцирования дженерика. Вся информация в принципе есть; особой тяжести здесь нету — операция выполняется единожды для каждого типа.
Ах, да, это, конечно же, приводило бы все же к некоторому code bloat. Потому как для всех референс-типов джит вроде как использует единый код. Поэтому вызовы всех методов в рамках дженерик-кода должны быть отрезолвлены статически еще при компиляции.
А для дак-тайпинга все вызовы дак-методов придется подшаманивать.
Хотя можно схитрить еще и примерно так: раз уж мы ограничили утиность дженериками, оставим сами интерфейсы нормальными. Вот пример:
public interface IDuck
{
void Quack();
}
Сделаем нормальную реализацию этого интерфейса
public class DuckImplementation : IDuck
{
public void Quack()
{
System.Console.WriteLine("I'm a normal duck");
}
}
И "случайную" реализацию, оборудованную подходящим методом:
public class OccasionalDuck // nothing implemented!
{
public void Quack()
{
System.Console.WriteLine("Um, er, I think I occationally appear to be, er, duck ;)"
}
}
В теперешнем шарпе2 мы можем сделать только так:
public class DuckUser<Duck>
where Duck: IDuck
{
public static MakeQuack(Duck duck)
{
duck.Quack();
}
}
При попытке инстанцировать DuckUser<OccationalDuck> мы получим компайл-тайм еррор.
А вот как это могло бы работать в гипотетическом шарпе4:
public class DuckDuckUser<T>
where T: duck IDuck
{
public static MakeQuack(Duck duck)
{
duck.Quack();
}
}
При инстанцировании такого дженерика, джит сначала проверит наличие настоящей поддержки IDuck. В таком случае можно использовать тот же самый код с нормальным приведением через таблицу интерфейсов.
А вот при параметризации OccasionalDuck, джит будет должен сочинить новый код для метода MakeQuack, где будет прямой биндинг к OccasionalDuck.Quack. В принципе, ничего особо страшного — сигнатура будет проверена на корректность, и все, кроме самого call/callvirt, можно оставить на местах.
Напоследок еще одна мысль.
Очень мне нравится шарп тем, что я могу очень легко делать почти-дак тайпинг. В терминах предыдущего примера:
public class NotSoOccasionalDuck: OccasionalDuck, IDuck {}
Все, этим не таким случайным утком уже можно параметризовать DuckUser. Шарп видит совпадение сигнатур, и успешно принимает реализацию "от папы". Есть некоторые неприятные тонкости — например, с недефолтными конструкторами, sealed классами и прочими областями видимости. Ну, и, собственно, с приведениями — у нас же все методы, в которые мы хотели передавать T (ну то есть OccasionalDuck), теперь хотят принимать только NotSoOccasionalDuck. Но можно поупражнять фантазию в этом направлении.
... << RSDN@Home 1.1.4 stable rev. 510>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Мне тут подумалось, что duck typing начинается там, где мы явно начинаем адаптироваться к окружающим условиям.
Т.е., если мы имеем:
class Animal { public : virtual void voice() = 0; };
void
get_voice( Animal & beast ) { beast.voice(); }
class Duck : public Animal { public : virtual void voice() { crack(); } ... };
class Dog : public Animal { public : virtual void voice() { gaff(); } ... };
то это обычный полиморфизм.
Если есть:
template< class A > void
get_voice( A & beast ) { beast.voice(); }
class Duck { public : void voice() { crack(); } ... };
class Dog { public : void voice() { gaff(); } ... };
то это обычное обобщенное программирование (статический полиморфизм в духе C++).
А вот если мы начинаем адаптироваться к типам:
def get_voice( beast )
voice( beast )
end
class Duck; def crack; ... end; end;
class Dog; def gaff; ... end; end;
def voice( beast )
if beast.respond_to( "crack" )
beast.crack()
elfif beast.respond_to( "gaff" )
beast.gaff()
end
end
то это уже duck typing и есть. Этим то он от других форм полиморфизма и обобщенного программирования отличается. ИМХО.
Причем не важно, с помощью чего мы адаптацию делаем -- с помощью метода respond_to в run-time или специализации шаблонов в compile-time.
Последнее замечание, имхо, показывает, что duck typing вполне возможен в статически типизированных языках. Например, в C++ (хотя он и сейчас доступен, но слишком геморройно):
/* Ключевое слово duck_typing вместо template :) */
duck_typing< T, void T::crack() >
void voice( T & beast ) { beast.crack(); }
duck_typing< T, void T::gaff() >
void voice( T & beast ) { beast.gaff(); }
template< class T >
void get_voice( T & beast ) { voice( beast ); }
Здравствуйте, Зверёк Харьковский, Вы писали:
E>>Код load_header_and_data стал понятнее. А хак, хоть и спрятанный, остался.
ЗХ>Я предвидел такой ответ
ЗХ>Тем не менее, я, скажем, могу представить случай, в котором функция seek у объекта будет, но, скажем, заведомо неэффективная (или и вовсе — с тем же именем, но с другим смыслом).
ЗХ>здесь возникает философский вопрос: а кто должен знать нужный способ? Мне (пока) наиболее изящным и, скажем так, прямым решением кажется концепция traits: ЗХ>
ЗХ>Здесь и интерфейс stream не раздувается, но и решение принимается "тем, кто знает как решать". ЗХ>Грубо говоря, если появится еще один тип потока (предположенный мной), у которого есть функция seek, но смысл ее другой, а сдвигаться вперед надо функцией move — то нужно всего лишь описать traits для типа этого потока, не меняя клиентский код.
Да, но если речь идет о C++, то тебе придется перекомпилировать клиентский код. И в момент компиляции ты должен будешь предоставить компиляторы все существующие специальзации traits.move_forward, чтобы компилятор смог выбрать наилучшую.
В Ruby аналогом этого можно сделать:
# В старом move_forward были предыдущие специализации.
alias :old_move_forward :move_forward
# А это новая специализация.
def move_forward(stream, bytes)
if stream.respond_to? :some_unique_seek
stream.some_unique_seek bytes
else
# В противном случае пользуемся старыми специализациями.
old_move_forward stream, bytes
end
end
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Зверёк Харьковский, Вы писали:
E>>А тебе не кажется, что эта специализация, фактически, эквивалентна приведенному мной выше skip_data?
ЗХ>Казалось бы. Ан нет. ЗХ>Твой вариант — при появлении нового типа потока, требует модификации функции skip_data (которая может быть и недоступна).
Причем это можно делать даже без останова приложения.
ЗХ>Мой вариант — при появлении нового типа потока создаем еще одну (отдельную и независимую специализацию).
Но доступную только во время компиляции.
ЗХ>Второе отличие — очередное проявление "статика vs. динамика". В моем варианте, если неизвестно, как обрабатывать новый тип потока (нет специализации seek) — мне моментально ругнется компилятор. В твоем — если функция skip_data не учла новый тип потока — ругнется рантайм "когда-нибудь" — может быть, через 5 лет использоания
Ты знаешь, я подумал, что наши решения в принципе эквивалентны, с точностью до разницы между концепциями is-a и can
Т.е. по сути, на псевдокоде их сущность будет выглядеть так:
//мое, на C++
if (stream is-a file)
stream.seek();
else if (stream is-a socket)
stream.read();
//твое, на Ruby
if (stream can seek)
stream.seek();
else if (stream can read)
stream.read();
"Синтаксический сахар" компилятора делает мое решение выглядящим слегка изящнее.
В целом — все аналогично.
Здравствуйте, Sinclair, Вы писали:
VD>>CanSeek — это свойство. И дело тут даже не в базовости потока, а втом, что когда их разрабатывали дженериков еще небло. А так можно было бы в принципе сделать иерархию потоков. S>Вот с этого места я вообще не понимаю.
Не ясно что именно не понимашь. Если про иерархию, то ниже я описал свое видение.
VD>>Однко нафига тут дженерики не ясно. S>А вот с этого — уже понимаю
Ну, это главное.
S>А ну как кто-то меня интом параметризовать вздумает? Где смысл, где логика?
Ну, от этого можно защититься констрэйном (только они для этого должны быть).
S>Ну, я все едино даункасты не люблю.
Дык что значит люблю не люблю? Не пиши код динамически разбирающийся в типе потока. Пиши код универсальный.
S> Но сама идея, имхо, в целом правильная. Тогда можно строить алгоритмы, которые честно декларируют свои требования в виде типов параметров, а не в виде документации.
Именно. А в примере eao197 уже была логика распознования типа. Вот для нее прийдется исползовать приведение типов, но все же это будет безопасный и понятный код. Причем без всяких выкрутасов и кодогенераций:
IRandomInputStream randomStream = stream as IRandomInputStream;
if (randomStream == null)
проматываем стрим прочитыванием...
else
randomStream.Seek(...);
Такой код уже можно считать рантайм оптимизацеией. Он будет корректно работать с любым типом InputStream-а. Просто с IRandomInputStream он будет работать быстрее. Причем пользователю функции не прийдется думать о комромисе, так как он просто отдаст нужный поток, а алгоритм сам поймет можно ли работать умнее.
В примере eao197 по всей видимости подразумевалась кодогенерация. При этом пришлось бы или явно приводить тип перед вызовом фунции или оптимизация просто не сработала бы так как использовалась бы статитческая информация о типах.
S>...можно было бы делать S>
Именно. Статическая типизация в действии. К сожалению не разумное проектирование интерфейсов бичь очень многих программистов. На ту же Сцинтилу без слез не взглянешь. Причем объем кода у них больше, а количество классов меньше. Парадокс, однако.
S>Однако, как я подозреваю, микрософтеры сделали стрим таким потому, что считают свойства CanXXX не неотъемлемо приданными на уровне класса, а изменчивыми за время жизни одного и того же стрима. Это — единственное оправдание подобной архитектуры.
Думаю, что они это сделали по глупости. Точно так же как использовали базовый класс вместо интерфейса.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Кодёнок, Вы писали:
Кё>Кстати, динамическая типизация тут не обязательна. В С++ возможно объявить шаблонную функцию, которая будет рассчитывать на наличие определенных методов у своих аргументов — и это будет то же самое (только эти требования нигде в коде явно не прописаны, что плохо).
Немного оффтоп, но к слову считаю что это "упущение" является одним из принципиальных ляпов при введении шаблонов в C++.
Ведь что стоило ввести описание типа и его свойств доступных данному шаблону ? Кокоенить ключевое слово похожее на виртуал типа "templated".
Данвно , года 4 назад я был ярым сторонником шаблонов в C++ , видел их потенциал и т.д. Но этот ляп явился одним из решающих , который позволяет мне относиться к шаблонам с большой опаской (есть и другие недостатки).
S>>И тут уж ты не ошибешься.
VD>Именно. Статическая типизация в действии. К сожалению не разумное проектирование интерфейсов бичь очень многих программистов.
А другой бичь, имхо, в попытке привести разные типы к одинаковым интерфейсам.
Как раз мне кажется, что duck typing, это разновидность обобщенного программирования, при котором устранение несоотвествия между типами устраняется более простым способом.
И нужно написать функцию, которая может взять N байт из объекта любого из этих типов. Эти классы не имеют общего интерфейса, да и вряд ли вообще должны.
Варианты с наследованием или со стратегиями требуют создания отдельных классов или иерархий классов, т.е. порождения новых сущностей только для того, чтобы привести уже существующие сущности к одному знаменателю. А подход на основе duck typing не требует этого:
def get_n_bytes( device, bytes )
if device.respond_to? :read then return device.read bytes
elsif device.respond_to? :get then return device.get bytes
elsif device.respond_to? :peek then return device.peek bytes
else
fail "unknown beast: #{device.class.name}"
end
end
Хорош такой подход или нет, но он точно позволяет отказаться от введения новых типов.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
minorlogic wrote:
> Немного оффтоп, но к слову считаю что это "упущение" является одним из > принципиальных ляпов при введении шаблонов в C++. > Ведь что стоило ввести описание типа и его свойств доступных данному > шаблону ? Кокоенить ключевое слово похожее на виртуал типа "templated".
Нечто такое собираются добавить в следующий Стандарт С++ под названием
"Concepts".
> Данвно , года 4 назад я был ярым сторонником шаблонов в C++ , видел их > потенциал и т.д. Но этот ляп явился одним из решающих , который > позволяет мне относиться к шаблонам с большой опаской (есть и другие > недостатки).
Нынешние шаблоны были спроектированы в 93 году Так что вполне
понятно, что они не получились абсолютно идеальными.
Здравствуйте, VladD2, Вы писали:
VD>Ну, вот С++ со своими шаблонами сегодня живет по принципу "like-a". Что приводит к забавнейшему поведению компилятора. На такой вот простой код: VD>
VD>Он реагирует выдачей следующего сообщения об ошибке: VD>
e:\vs\vs2005\vc\include\xutility(1059) : error C2665: 'std::_Debug_range2' : none of the 2 overloads could convert all the argument types
...
VD> ]
VD>Причем указывает он при этом не на строку с ошибкой, а на строку 1059 в файле e:\vs\vs2005\vc\include\xutility
VD>Между тем проблема лекго решается путем введения интерфейса.
Еще лучше она решается введением concept-ов. Причем для этого даже не нужно ждать C++0x (уж извини за длинный код, оставил только самое нужное):
template< class T >
struct sort_concept_test
{
static void constraint( T a )
{
++a;
T b = a;
b = a;
*b = *a;
bool l = *a < *b;
}
sort_concept_test() { void (*p)(T) = constraint; }
};
template< class T >
void my_sort( T a, T b )
{
sort_concept_test< T >();
std::sort( a, b );
}
int
main()
{
int i, j;
my_sort( i, j );
}
Получаем:
sort_2.cpp
sort_2.cpp(13) : error C2100: illegal indirection
sort_2.cpp(7) : while compiling class-template member function 'void sort_concept_test<T>::constraint(T)'
with
[
T=int
]
sort_2.cpp(23) : see reference to class template instantiation 'sort_concept_test<T>' being compiled
with
[
T=int
]
sort_2.cpp(32) : see reference to function template instantiation 'void my_sort<int>(T,T)' being compiled
with
[
T=int
]
sort_2.cpp(13) : error C2100: illegal indirection
sort_2.cpp(14) : error C2100: illegal indirection
sort_2.cpp(14) : error C2100: illegal indirection
Если же заменяем вызов my_sort:
std::vector< int > i;
my_sort( i.begin(), i.end() );
То все компилится как надо.
Вот почему в STL такие concept-ы сейчас не используются -- это другой вопрос. В Boost-е уже давно готовая библиотека есть: Boost.ConceptCheck.
E>>Да, но особенности Lisp-а и Smalltalk-а не позволили им набрать большого количества сторонников. А вот Python и Ruby популярность набирают.
VD>Вся история программирования — это борьба с подходами вроде утиной типизации. А ты призывашь обратно в каменный век.
История развивается по спирали. Не зря же сейчас интерес к динамическим языкам возврастает.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, VladD2, Вы писали:
D>>Более того, типизация несколько мешает ОО. Чистое объектное видение мира подразумевает, что любой объект потенциально может отреагировать на любое сообщение. Или другими словами есть только объекты и объекты не различаются ничем, кроме своего поведения. Например, к каждому человеку можно подойти с вопросом "Сколько ты будешь делать?" с параметром ТЗ. На что можно получить в ответ "N дней", "Чего???" или по зубам . Но коллапса с миром не случит в любом случае.
VD>Вот здесь уже противоречие. С одной стороны "объекты не различаются ничем, кроме своего поведения", с другой походить ты хочешь к "каждому человеку", а не, к примеру, к столбу. Ведь у столба ты вряд ли сможешь спросить что либо, и уж точно не сможешь дать ему по зубам. Так что класс объекта определяет его интерфейс. И если мы заранее знаем класс объекта, то можем не далать бессмысленных действий.
Спросить могу, другое дело, что в ответ ничего не будет, но всеравно могу, и ударить могу тоже. Конечно введение типов помогает узнать, что с объектном делать, можно, а что нельзя. Но так же типы мешают применять некоторые решения, например в DolphinSmalltalk есть класс DeafObject. Объектны этого класса реагируют на все сообщения и возвращают себя же. Применяется как замена кода типа:
if (object.hasValue()) {
value = object.getValue();
// Работаем c value.
}
на
value = object.getValue();
// Работаем c value. Если value это экземпляр DeafObject, то вся работа с value будет проигнорированна.
Только, пожалуйста, не надо комментировать этот паттерн, я в курсе, без него можно обойтись, а применять стоит очень осторожно.
Другое применение: логгер вызванных методов — иногда очень удобно в тестах.
VD>Например, если мы заблудились в лесу, то мы не будем подходить к каждому дереву и спрашивать "как выйти к станции?", за то мы будем подходить с подобным вопросом к каждому всречному человеку.
При наличии света в лесу, деревья шлют тебе сообщения, что они деревья. Дальше включается логика, которая позволяет не делать глупостей С другой стороны, при плохом освещении, может случиться, что ты пойдешь спрашиват к столбу (перепутаешь), и не пойдешь спрашивать у человека (не увидишь).
Здравствуйте, Dyoma, Вы писали:
D> Например, возврат (из метода), не является посылкой собщения вызывающему объектку.
Угу.
D>Так же выброс исключения это тоже не сообщение.
Ага. Не одно сообщение, а целая куча
Если серьёзно, подозреваю, что зависит реализации (изначально ведь никаких исключений не было). В VisualWorks исключения реализованы средствами языка.
<...> D>Вот так прыгаем между 3 и 4, пока не получаем, работающую или почти работающую фичу. D>Здесь уток выкидываем, а точнее переходим к пункту 1, а потом 2 — вписываем типы. А компилятор проверяет, что мы не пропусти ни один класс и везде все необходимые методы есть.
Имхо, была когда-то такая заповедь у программистов: никогда не переделывай работающий код
В этом свете мне не понятно, зачем после успешно завершенных шагов 3 и 4 переделывать уже работающий код для выполнения шагов 1 и 2?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
D>>Вот так прыгаем между 3 и 4, пока не получаем, работающую или почти работающую фичу. D>>Здесь уток выкидываем, а точнее переходим к пункту 1, а потом 2 — вписываем типы. А компилятор проверяет, что мы не пропусти ни один класс и везде все необходимые методы есть.
E>Имхо, была когда-то такая заповедь у программистов: никогда не переделывай работающий код E>В этом свете мне не понятно, зачем после успешно завершенных шагов 3 и 4 переделывать уже работающий код для выполнения шагов 1 и 2? E>
1. Не переделывать, а верифицировать компилятором (системой типов). Деятельность схожая с запуском написанного кода, дабы увидеть своими глазами, что оно таки работает
2. Списать типы не обязательно в самом конце, это можно сделать как только появится твердая уверенность, что дизайн устаканился и все грабли уже найдены и обходные пути известны. Т.е. когда появится ощущение, что дальнейший отказ от типов не даст выигрыша.
3. Добавить декларацию типов в качестве комментариев (документации)
Здравствуйте, Blazkowicz, Вы писали:
B>Влад что ты имеешь ввиду под "рантаймная сущьность"? Явские дженерики с рантаймом никак не связаны. Вообще.
То что код не становится специализированным в процессе компиляции.
Собственно Явовские дженерики это на половину обман, так как в рантайме все живет на динамическом полиморфизме. Но с другой стороны это не припятствует тому, чтобы дженерики помещались в библиотеки. В конце концов свою задачу они решают. Человек не обязан клепать кучу специализированных классов или явно пользоваться приведениями типов.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, eao197, Вы писали:
E>Еще лучше она решается введением concept-ов. Причем для этого даже не нужно ждать C++0x
Хреновенько. Я бы даже сказал чуть-чуть замазывается, а не решается.
E>(уж извини за длинный код, оставил только самое нужное):
Да, в этот раз как раз более-менее коротко. Всегда бы так. E>Получаем:
А получаем мы ту же задниц, вид с боку. Чуть лучше чем было, но все равно задница.
О чем говорит это сообщение об ошибке?
E>[q] E>sort_2.cpp E>sort_2.cpp(13) : error C2100: illegal indirection E> sort_2.cpp(7) : while compiling class-template member function 'void sort_concept_test<T>::constraint(T)'
E>Вот почему в STL такие concept-ы сейчас не используются -- это другой вопрос. В Boost-е уже давно готовая библиотека есть: Boost.ConceptCheck.
Ну, в STL только интерфейс описан. А реализация может быть любой. В том числе и с концептами. Думаю, когда появятся полноценные коцепты, то их сразу же вставят в большинство реализаций.
Только все равно концепты не приучат народ использовать их во всех шаблонах и описанная ситуация еще не раз повторится.
VD>>Вся история программирования — это борьба с подходами вроде утиной типизации. А ты призывашь обратно в каменный век.
E>История развивается по спирали. Не зря же сейчас интерес к динамическим языкам возврастает.
Именно, что по спирали! Но если история развивается по груку, то это не развитие. Вот идея концептов, коие на самом деле являются ни чем иным как утиными интерфейсами — это действительно развите по спирали. Если к ней еще прикрутить некую идею мапинга типа на утиный интерфейс, то будет вообще круто. Если не ясно о чем я говорю, то поясню. Предположим у нас есть некий тип который нельзя напрямую ассоциировать с утиным интерфейсом, ну, например, у него имя метода не совпадает или у метода лишний параметр. Чтобы этот тип можно было использовать там где допустим утиный интерфейс прийдется применить классический ОО-паттерн — обертку (врапер). Но каждый раз явно создавать эту обертку некрасиво, да и не эффективно. Вместо этого можно было бы дабавить некий синтаксис который позволил бы указать компилятору что при попытке привести тип к утиному интерфейсу нужно (а может и к полноценному если поддержка таковых в языке есть) нужно использовать эту обертку. При этом можно будет задать еще и некотороую информацию о некоторых тонкостях маппинга (а может это будет и не нужно).
Однако то что есть в С++ сегодны (да и в Руби) — это попрание всех принципов безопасного и понятного программирования. Так что вводя в язык концепты и интерфейсы нужно ко пвсему прочему удалять из него "возможности" неявногой утиности. В общем, "Маг.Дак маст дай".
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Можешь. Вот только тогда уже можно задуматься о твоей адекватности. Тех кто часто такими вещами занимается, обычно кладут в соотвествующие лечебные заведения.
В жизни от неадекватного поведения спасает собственный разум и самоконтроль. В программировании компилятор и рантайм. Это же удобно. Представь как сожно будет программировать если контроль исчезнет? Это же будет хуже ассемблера.
D>Только, пожалуйста, не надо комментировать этот паттерн, я в курсе, без него можно обойтись, а применять стоит очень осторожно.
Ну, ты сам все сказал. Мне добавить нечего.
D>Другое применение: логгер вызванных методов — иногда очень удобно в тестах.
А что логер? Смолток не единственный язык где "все является объектом". В C# у меня тоже все является объектом и если мне понадобится, то я смогу написать универсальный код. Например, Console.WriteLine() прекрасно отображает информацию о любом объекте. Правда тут скорее используется полиморфизм, но все же. Главное, что передаю я ему все приводя к типу object. Ну, и как последний аргумент — рефлексия. С ее помощью любой логе и т.п. пишется как нефиг делать. Заметь все эти идеи позаимствованы у Смолтока, но при этом они прекрасно работают в статически типизированном языке. И отлично, надо сказать, работают!
D>При наличии света в лесу, деревья шлют тебе сообщения, что они деревья.
Это надо с дубу рухнуть, чтобы разговаривать с деревьями.
Мы анализируем деревья с помощью данных нам органов чувств (больше зрением). Далее мы принимаем решение о типе объекта на который мы смотрим, и принимаем решение, что объект данного типа на нужный нам запрос не ответит.
D> Дальше включается логика, которая позволяет не делать глупостей
У вас, у любителей Смотока, наблядается некий сдвиг по фазе. Видимо это от неконтролируемости. Шучу.
D> С другой стороны, при плохом освещении, может случиться, что ты пойдешь спрашиват к столбу (перепутаешь), и не пойдешь спрашивать у человека (не увидишь).
Погоди ка. Он же тебе сообщения шлет! Зачем же тебе к нему ходить? Или все же я прав?
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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>>>А я про типизацию-то в данной фразе и не говорил. Я имел в виду, что в ООП мы используем отношение "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 унести) ...
В этом случае от унесения не застрахован любой предмет с ручкой В том числе — дверь, если плохо прикреплена
Здравствуйте, Зверёк Харьковский, Вы писали:
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++.
Здравствуйте, 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-тесты, что характерно, гарантировать тоже ничего не смогут, поскольку успешность операции может быть плохо формализуема).
Здравствуйте, 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>И вместе с тем контроль контракта по публичному интерфейсу компилятор выполняет бесплатно. Думаю это стоит некоторого неудобства при кодировании (именно при кодировании, на архитектуру это влияет слабо).
Здравствуйте, 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>Угу. Мы опускаем, а все что было создано до нас и не нами на иерархии очень надеется.
Помимо наследования есть масса паттернов для реюза кода.
Здравствуйте, eao197, Вы писали:
AVK>>Есть. Только сходство с наследованием классов чисто внешнее. По сути же это просто такое ограничение.
E>Правда?
Правда.
E> А зачем тогда наследование интерфейсов вообще?
Удобный констрейнт, позволяющий к тому же делать прозрачный downcasting.
AVK>>Нет, не то же самое. Все намного глубже и интереснее. На самом деле наличие реализовываемого интерфейса означает что компилятор создает специальную таблицу, interface map, которая отображает методы интерфейса на методы класса.
E>Тоже самое, имхо, происходит в C++ когда мы наследуемся от абстрактного класса без данных. Таблицу виртуальных функций ведь не я сам строю.
Нет, не то же самое (если лезть в детали, то вызов интерфейса обладает двойной косвенностью, а не одинарной, как в случае обычного виртуального вызова). В случае наследования класса мы приобретаем массу всего — код, данные. В случае реализации интерфейса ничего такого не происходит, мы всего лишь декларируем то, что некий класс обладает определенным публичным интерфейсом и ничего сверх того.
AVK>>Нет. Прелесть исключений в том что они детерминированы и локализованы. Т.е. осыпаться будет всегда и строго в том месте, где имеется проблема.
E>Точно так же и в duck typing.
Нет. Потому что несовместимость может быть семантической (сигнатура слабо связана с семантикой!), а в этом случае результат непредсказуем.
E> Как только ставим на подставку груз больше расчетного -- получаем исключение. Все то же самое.
А если это не чемодан, а люк мусоропровода? Получим что телевизор улетит в неизвестном направлении.
AVK>>Не забывай о том, что львиная доля проверок выполняется в compile time.
E>Если duck typing встраивается в статически типизированный язык, то получаем те же самые проверки.
Нет, не получаем, потому что нет формального контракта, связанного с сигнатурой. Совпадение сигнатуры еще ничего не означает. А если мы такой формальный контракт введем, то получим интерфейсы — вид сбоку.
Здравствуйте, AndrewVK, Вы писали:
E>> А зачем тогда наследование интерфейсов вообще?
AVK>Удобный констрейнт, позволяющий к тому же делать прозрачный downcasting.
Ну да ладно.
Хотя тоже самое можно сказать и про обычное наследование. В смысле прозрачного downcasting-а.
AVK>>>Нет. Прелесть исключений в том что они детерминированы и локализованы. Т.е. осыпаться будет всегда и строго в том месте, где имеется проблема.
E>>Точно так же и в duck typing.
AVK>Нет. Потому что несовместимость может быть семантической (сигнатура слабо связана с семантикой!), а в этом случае результат непредсказуем.
Я имел в виду, что мы получаем исключение в результате действия, начатого в каком-то методе поставить_телевизор_на_опору. Этот метод не выполнит своих действий, а уже из-за чего -- это дело следователей. То, что исключение порождено в Чемодан.принять_груз или в СтеклянныйЖурнальныйСтолик.принять_груз -- это уже последствия того, что метод поставить_телевизор_на_опору выбрал неподходящую подставку. И наличие интерфейсов здесь нам никак не поможет. Если только мы не ввидем классификацию вроде: ненадежные_подставки, надежные_подставки, очень_надежные_подставки. Хотя это уже явно маразматический путь развития.
E>> Как только ставим на подставку груз больше расчетного -- получаем исключение. Все то же самое.
AVK>А если это не чемодан, а люк мусоропровода? Получим что телевизор улетит в неизвестном направлении.
А какая разница? Повторю, что метод поставить_телевизор_на_опору почему-то посчитал, что опора для этих целей подходит. А она не подошла.
E>>Если duck typing встраивается в статически типизированный язык, то получаем те же самые проверки.
AVK>Нет, не получаем, потому что нет формального контракта, связанного с сигнатурой. Совпадение сигнатуры еще ничего не означает. А если мы такой формальный контракт введем, то получим интерфейсы — вид сбоку.
+-
Все же осмысленная сигнатура много чего значит. И если у разных классов сигнатуры методов совпадают, то это не спроста.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, AndrewVK, Вы писали:
AVK>>>Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
E>>Да нет, как раз в том и суть duck typing, что это уже и не ОО.
AVK>Контракт это тоже не обязательно ОО.
Согласен, но у тебя было написано ОО-контракт.
E>>Поэтому оценивать duck typing с точки зрения ОО не совсем корректно.
AVK>Зачем тогда вы его описываете с точки зрения ОО? И что тогда за тип имеется ввиду (duck typing)?
Так ведь я не зря озаглавил тему "Утиные истории vs ООП". Чтобы понять, как между собой соотносятся ООП и duck typing при разработке приложений.
Под типом понимается объект с методами. Но если наследование -- это один из постулатов ООП, то в Duck typing можно обходится и без наследования.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
AVK>>Удобный констрейнт, позволяющий к тому же делать прозрачный downcasting.
E>Ну да ладно. E>Хотя тоже самое можно сказать и про обычное наследование. В смысле прозрачного downcasting-а.
Нельзя. Потому что обычное наследование это много большее, нежели декларация совместимости публичных контрактов.
AVK>>Нет. Потому что несовместимость может быть семантической (сигнатура слабо связана с семантикой!), а в этом случае результат непредсказуем.
E>Я имел в виду, что мы получаем исключение в результате действия, начатого в каком-то методе поставить_телевизор_на_опору. Этот метод не выполнит своих действий, а уже из-за чего -- это дело следователей.
Опять же неформализуемо. Как определить выполнено ли действие, если чужой класс черный ящик? И как определить что он попутно не выполнил чего лишнего? А как сходу понять, что исключение "ЧемоданЭбсолютлиКрешед" из-за того, что мы на него телевизор поставили, а не потому что вчера соседский кот туда нассал?
E> То, что исключение порождено в Чемодан.принять_груз или в СтеклянныйЖурнальныйСтолик.принять_груз -- это уже последствия того, что метод поставить_телевизор_на_опору выбрал неподходящую подставку. И наличие интерфейсов здесь нам никак не поможет.
В случае наличия интерфейсов вместо сообщения о поломке чемодана мы получим сообщение о том, что телевизор на чемодан ставить нельзя. Сразу все понятно.
AVK>>А если это не чемодан, а люк мусоропровода? Получим что телевизор улетит в неизвестном направлении.
E>А какая разница?
Большая. Люк может сразу открыться, а может через час, когда туда чего выбрасывать будут. Если второй вариант, то ты потом долго искать тараканов, уперших телек.
E> Повторю, что метод поставить_телевизор_на_опору почему-то посчитал, что опора для этих целей подходит. А она не подошла.
А как определить что не подошла? Каков формальный критерий подходимости? Вот с интерфейсом все просто — если реализован, значит создатель класса рассчитывал на такое применение. И если будут проблемы, то это уже банальный баг в этом классе.
E>Все же осмысленная сигнатура много чего значит.
Тем не менее полагаться на это нельзя. В случае интерфейса в документации явно сказано что он означает. А вот под сигнатурой каждый производитель может понимать что то свое.
Здравствуйте, eao197, Вы писали:
AVK>>Контракт это тоже не обязательно ОО.
E>Согласен, но у тебя было написано ОО-контракт.
Потому что твое описание тоже от ОО плясало.
AVK>>Зачем тогда вы его описываете с точки зрения ОО? И что тогда за тип имеется ввиду (duck typing)?
E>Так ведь я не зря озаглавил тему "Утиные истории vs ООП". Чтобы понять, как между собой соотносятся ООП и duck typing при разработке приложений.
Да пофигу как озаглавил. Главное что внутри топика ты от ОО не отходишь.
E>Под типом понимается объект с методами.
Ну все, дальше можно говорить только в терминах ООП.
E> Но если наследование -- это один из постулатов ООП,
Знаешь, я в этом не уверен. Впрочем это терминологический спор.
Здравствуйте, AndrewVK, Вы писали:
E>>Ну да ладно. E>>Хотя тоже самое можно сказать и про обычное наследование. В смысле прозрачного downcasting-а.
AVK>Нельзя. Потому что обычное наследование это много большее, нежели декларация совместимости публичных контрактов.
В случае абстрактных базовых классов в C++, в которых нет ничего кроме чистых виртуальных методов, разницы между наследованием и реализацией интерфейса нет никакой. Поэтому я и считаю, что interface в Java -- это не более, чем удобный синоним длинному определению "абстрактный класс, в котором есть только чистые виртуальные методы" в C++. А все остальное то же самое.
AVK>Опять же неформализуемо. Как определить выполнено ли действие, если чужой класс черный ящик? И как определить что он попутно не выполнил чего лишнего?
С интерфейсом все то же самое. Или уже есть интерфейсы, где явно говорится, что вот этот метод не имеет побочных эффектов?
AVK> А как сходу понять, что исключение "ЧемоданЭбсолютлиКрешед" из-за того, что мы на него телевизор поставили, а не потому что вчера соседский кот туда нассал?
Потому, что мы получили это исключение в результате действия "поставить чемодан на опору". А если бы соседский кот туда не нассал, может и выдержал бы, а так -- подгнил чуточку.
E>> То, что исключение порождено в Чемодан.принять_груз или в СтеклянныйЖурнальныйСтолик.принять_груз -- это уже последствия того, что метод поставить_телевизор_на_опору выбрал неподходящую подставку. И наличие интерфейсов здесь нам никак не поможет.
AVK>В случае наличия интерфейсов вместо сообщения о поломке чемодана мы получим сообщение о том, что телевизор на чемодан ставить нельзя. Сразу все понятно.
Да это понятно. Меня волнует то, что мы не получим никакого предупреждения при попытке поставить телевизор на СтеклянныйЖурнальныйСтолик. Хотя понятно, что мы выбираем неподходящую опору. Как и в случае с чемоданом.
E>> Повторю, что метод поставить_телевизор_на_опору почему-то посчитал, что опора для этих целей подходит. А она не подошла.
AVK>А как определить что не подошла? Каков формальный критерий подходимости?
Не случилось исключения. И все работает после действия как надо.
AVK> Вот с интерфейсом все просто — если реализован, значит создатель класса рассчитывал на такое применение. И если будут проблемы, то это уже банальный баг в этом классе.
Ну вот нет в ситуации со СтекляннымЖурнальнымСтоликом бага. Наоборот, его разработчик правильно реализовал проверку веса, который столик может выдержать.
Здесь проблемы у того, кто этот класс для неподходящих целей использует.
E>>Все же осмысленная сигнатура много чего значит.
AVK>Тем не менее полагаться на это нельзя. В случае интерфейса в документации явно сказано что он означает. А вот под сигнатурой каждый производитель может понимать что то свое.
Но ведь, если мы знаем с объектами каких типов нам предстоит работать (вспомним, что программа -- это все таки реализация одной часной задачи) и оказывается, что методы с одинаковыми сигнатурами в них выполняют нужные нам действия, то мы просто их используем.
В случае же с интерфейсами мы находимся в подобной ситуации. Сначала мы должны понять, какой интерфейс нам использовать. Потом убедится, что все нужные нам классы этот интерфейс реализуют. Затем, что реализуют их так как нам нужно (без нежелательных побочных эффектов). Если кто-то не реализует, а нам хотелось бы, то мы делаем вокруг него обертку. Чтобы в конце-концов получить тот же самый результат.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, AndrewVK, Вы писали:
E>>Так ведь я не зря озаглавил тему "Утиные истории vs ООП". Чтобы понять, как между собой соотносятся ООП и duck typing при разработке приложений.
AVK>Да пофигу как озаглавил. Главное что внутри топика ты от ОО не отходишь.
А зачем мне отходить, если я хочу понять, где у duck typing-а есть преимущества над ООП, а где недостатки.
E>>Под типом понимается объект с методами.
AVK>Ну все, дальше можно говорить только в терминах ООП.
E>> Но если наследование -- это один из постулатов ООП,
AVK>Знаешь, я в этом не уверен. Впрочем это терминологический спор.
ООП базируется на трех китах: инкапсуляция, наследование и полиморфизм.
Термин ООП я употребляю именно в этой интерпритации. Поэтому, если есть объект и есть методы (с полиморфизмом), но нет наследования (в принципе), то это уже не ООП.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>В случае абстрактных базовых классов в C++, в которых нет ничего кроме чистых виртуальных методов, разницы между наследованием и реализацией интерфейса нет никакой.
Не С++ единым. На момент создания С++ концепция интерфейсов еще не оформилась, в результате язык ее не поддерживает, а вместо этого предлагает способы ее эмуляции. Отсюда нет никакого контроля со стороны компилятора за тем, что абстрактный класс не содержит неабстрактных методов и полей.
AVK>>Опять же неформализуемо. Как определить выполнено ли действие, если чужой класс черный ящик? И как определить что он попутно не выполнил чего лишнего?
E>С интерфейсом все то же самое.
Нет. Потому что интерфейс это не просто синтаксическая конструкция, это вполне конкретная сущность с вполне конкретной семантикой.
E> Или уже есть интерфейсы, где явно говорится, что вот этот метод не имеет побочных эффектов?
В описании интерфейса.
AVK>> А как сходу понять, что исключение "ЧемоданЭбсолютлиКрешед" из-за того, что мы на него телевизор поставили, а не потому что вчера соседский кот туда нассал?
E>Потому, что мы получили это исключение в результате действия "поставить чемодан на опору". А если бы соседский кот туда не нассал, может и выдержал бы, а так -- подгнил чуточку.
Ну вот о том и речь, что нужно гадать что там произошло и по какой причине.
AVK>>А как определить что не подошла? Каков формальный критерий подходимости?
E>Не случилось исключения.
Фиговый критерий, надо сказать.
E> И все работает после действия как надо.
А как надо? Как это проконтроллировать? Опять перекладывать все на плечи программиста, т.е. юнит-тесты писать?
AVK>> Вот с интерфейсом все просто — если реализован, значит создатель класса рассчитывал на такое применение. И если будут проблемы, то это уже банальный баг в этом классе.
E>Ну вот нет в ситуации со СтекляннымЖурнальнымСтоликом бага. Наоборот, его разработчик правильно реализовал проверку веса, который столик может выдержать.Здесь проблемы у того, кто этот класс для неподходящих целей использует.
Наличие реализации интерфейса означает что класс рассчитан на использование этого интерфейса. Если класс не выполняет заявленный контракт, то это проблемы этого класса. А вот обеспечивать какую то интерпретацию определенной сигнатуры разработчик этого класса не может принципиально.
AVK>>Тем не менее полагаться на это нельзя. В случае интерфейса в документации явно сказано что он означает. А вот под сигнатурой каждый производитель может понимать что то свое.
E>Но ведь, если мы знаем с объектами каких типов нам предстоит работать
А знаем ли?
E> (вспомним, что программа -- это все таки реализация одной часной задачи)
Что не мешает разрабатывать решение не одному человеку, а команде, и не одномоментно, а на протяжении определенного промежутка времени.
E> и оказывается, что методы с одинаковыми сигнатурами в них выполняют нужные нам действия, то мы просто их используем.
А как проверить что все существующие классы с такими сигнатурами делают то же самое? Эскпериментально проверять их все? И что делать, если потом кто то другой будет добавлять новые классы, ничего не зная о твоей механике?
E>В случае же с интерфейсами мы находимся в подобной ситуации. Сначала мы должны понять, какой интерфейс нам использовать.
Выбрать метод с нужной сигнатурой.
E> Потом убедится, что все нужные нам классы этот интерфейс реализуют.
Убедится что все нужные классы этот метод содержат.
E> Затем, что реализуют их так как нам нужно (без нежелательных побочных эффектов).
Затем, что реализуют именно то, что нужно.
Затем, что реализуют их так как нам нужно (без нежелательных побочных эффектов).
E> Если кто-то не реализует, а нам хотелось бы, то мы делаем вокруг него обертку.
Если кто-то не реализует, а нам хотелось бы, то мы делаем вокруг него обертку.
E> Чтобы в конце-концов получить тот же самый результат.
А если у нас несколько связанных методов (чуть более сложный контракт), то для duck typing повторяем все для каждого метода.
Как видишь, в случае duck typing все только сложнее.
Здравствуйте, eao197, Вы писали:
AVK>>Да пофигу как озаглавил. Главное что внутри топика ты от ОО не отходишь.
E>А зачем мне отходить, если я хочу понять, где у duck typing-а есть преимущества над ООП, а где недостатки.
Не может у него быть преимуществ над ООП. Эти понятия ортогональны.
E>>> Но если наследование -- это один из постулатов ООП,
AVK>>Знаешь, я в этом не уверен. Впрочем это терминологический спор.
E>Так я, вроде, уже давно обозначил позицию: Re[4]: Утиные истории vs ООП?
E>ООП базируется на трех китах: инкапсуляция, наследование и полиморфизм.
E>Термин ООП я употребляю именно в этой интерпритации. Поэтому, если есть объект и есть методы (с полиморфизмом), но нет наследования (в принципе), то это уже не ООП.
А теперь вернемся к топику — я постоянно упоминаю о том, что наследование в рассмотрение вопроса включать не стоит. Ты же его пытаешься постоянно втащить. И ты же заявляешь:
AVK>Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
E>Да нет, как раз в том и суть duck typing, что это уже и не ОО.
Так при чем тут, черт возьми, наследование, если я говорю о контракте? Что, duck typing отменяет понятие контракта (обрати внимание, не интерфейса, а контракта)?
Здравствуйте, Dyoma, Вы писали:
AVK>>Кому должны? Компилятор не в состоянии проконтроллировать это. А значит мы задачу по контролю семантической совместимости возлагаем на программиста.
D>Эта задача и так лежит на программисте, никакой компилятор не проверяет, что за бред написан в реализации такого-то метода.
Видишь ли, в случае интерфейсов мы делаем это ровно один раз, когда реализуем тот или иной интерфейс. Все, дальше не наша проблема. Мы использовали определенный контракт, если кто то его реализовал не так, как это предписано, это его личная проблема.
А в случае duck typing нам нужно проконтроллировать контракт каждого используемого метода, причем постоянно эту процедуру повторять в том случае, если список используемых типов расширяется. Веселье наступает, если duck typing начинает использоваться несколькими независимыми механиками поверх одного типа.
D> Проверка типов проверяет только есть ли у заявленного типа методы с таким-то именем, а уж для чего он использован — дело только программиста.
Ты про duck typing?
AVK>>Совсем не похоже. Перегрузка сосредоточена внутри одного класса,
D>Я ж начал того, что scope контракта расширяется, теперь это дело не одного класса/интерфейса, а всего проекта.
Контракта проекта с чем, позвольте спросить?
AVK>> так что если ты там что то напортачил, то сам дурак.
D>А как насчет пользователя этого метода? Особенно в ситуации, когда параметры перегруженных методов связаны по иерархии и явным приведением типа в месте вызова можно повлиять на то какой метод будет вызван?
Еще раз повторюсь — класс это черный ящик, что находится внутри него личное дело разработчика этого класса. Пользователю остается только в точности следовать объявленному контракту. А вот в случае duck typing у нас такого контракта нет. Никто тебе не гарантирует что метод Add добавляет элемент в коллекцию и нечего более во всех классах которые использует и будет использовать в будущем твой алгоритм. Нет такого места, где написано — во всем проекте метод Add делает это и только это. Или есть?
AVK>>Только вот с интерфейсами оно как то проще.
D>Согласен, что интерфейсы упрощают жизнь при реализации метода, но не надо забывать, что они же усложныют использование в непредусмотренных ситуациях.
Вот как бы тебе сказать. Тебе не кажется что использование класса в отличных от исходно запланированнх целей, да еще и без жесткого контроля, сродни хождению по минному полю без миноискателя?
AVK>> Видишь ли, тут проблема не столько в том, что методы случайно совпали по сигнатуре, сколько в том что разные программисты могут интерпретировать одну и ту же сигнатуру по разному. Что бы этого не произошло нужно конкретные сигнатуры описывать формально, с указанием в комментариях/документации что под этой сигнатурой понимается. Чувствуешь к чему пришли?
D>А теперь о "подпорках". Вот я взял один из Smalltalk`ов (DolphinSmalltalk) и посмортел среднюю длинну имени метода (по библиотеке). Среднее арифметическое по различным именам, получилось больше 16 символов. Общее число (различных) имен методов почти 13000. Согласись 13000 — это очень большой словарный запас в него очень много понятий (в нашем случае контрактов) может поместиться.
Прочти еще раз, плиз, то что я написал. Ты меня просто не понял.
D>Насчет коментрариев/документации — это опять же не зависит от наличия типов. Если ты вводишь какой-то новый контрак, ты должен дать другим возможность узнать что ты имел ввиду.
Безусловно. Но, что характерно, в одно месте, в описании интерфейса. Все, все остальные нарушать единожды объявленный контрак не имеют права. А duck typing предлагает подобный контракт в каждом используемом классе, и все такие классы надо контроллировать.
Здравствуйте, AndrewVK, Вы писали:
E>> Или уже есть интерфейсы, где явно говорится, что вот этот метод не имеет побочных эффектов?
AVK>В описании интерфейса.
Видимо, мы по разному понимаем понятие побочных эффектов.
AVK>Ну вот о том и речь, что нужно гадать что там произошло и по какой причине.
Да не нужно гадать, из-за чего чемодан развалился. Гадать нужно почему на него телевизор поставили.
AVK>>>А как определить что не подошла? Каков формальный критерий подходимости?
E>>Не случилось исключения.
AVK>Фиговый критерий, надо сказать.
А есть другие критерии?
E>> И все работает после действия как надо.
AVK>А как надо? Как это проконтроллировать? Опять перекладывать все на плечи программиста, т.е. юнит-тесты писать?
А как вообще контролировать, что программа работает? Если не брать какие-то формальные математические доказательства. Что мы делаем, чтобы убедится в том, что в ней нет ошибок?
AVK>>> Вот с интерфейсом все просто — если реализован, значит создатель класса рассчитывал на такое применение. И если будут проблемы, то это уже банальный баг в этом классе.
E>>Ну вот нет в ситуации со СтекляннымЖурнальнымСтоликом бага. Наоборот, его разработчик правильно реализовал проверку веса, который столик может выдержать.Здесь проблемы у того, кто этот класс для неподходящих целей использует.
AVK>Наличие реализации интерфейса означает что класс рассчитан на использование этого интерфейса. Если класс не выполняет заявленный контракт, то это проблемы этого класса.
Ну выполняет он, выполняет. Этот несчастный столик является "подставкой". И если на него поставить вазу или телефон, то он справляется со своей задачей.
Проблема в том, что нам здесь нужен был бы интерфейс "подставка под телевизор". Но его нет. Есть "подставка". На него понадеялись. Получили исключение. Если данную ветку программы не протестировали, свалились в production.
E>>Но ведь, если мы знаем с объектами каких типов нам предстоит работать
AVK>А знаем ли?
Кажется есть такое понятие: "локальность ссылок". Если бы не оно, программирование на динамических языках действительно представляло бы из себя сплошной глюкодром. Но ведь это не так. А не так потому, что вмешивается в дело "локальность ссылок" -- объекты используются в очень ограниченных контекстах, где легко отслеживать их типы.
Так я думаю, что пока в проекте подобная "локальность" существует, то мы действительно знаем с чем работаем. А вот если эта локальность нарушается в силу:
AVK>Что не мешает разрабатывать решение не одному человеку, а команде, и не одномоментно, а на протяжении определенного промежутка времени.
то тогда строгие, статически проверяемые интерфейсы, действительно, показывают себя с наилучшей стороны.
E>> и оказывается, что методы с одинаковыми сигнатурами в них выполняют нужные нам действия, то мы просто их используем.
AVK>А как проверить что все существующие классы с такими сигнатурами делают то же самое? Эскпериментально проверять их все? И что делать, если потом кто то другой будет добавлять новые классы, ничего не зная о твоей механике?
Но ведь программу перед запуском все равно придется проверять.
Да, это плохой критерий, я согласен с тобой.
Но, видимо, в случае duck typing-а другого нет.
<...перечисление действий в случае duck typing...>
AVK>А если у нас несколько связанных методов (чуть более сложный контракт), то для duck typing повторяем все для каждого метода. AVK>Как видишь, в случае duck typing все только сложнее.
Может быть. Но duck typing начинает оправдываться, если у нужных нам типов нет изначально одинакового интерфейса -- ну не мы эти типы делали. В duck typing-е нам не потребуется унифицировать эти различия в общем интерфейсе.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, AndrewVK, Вы писали:
E>>А зачем мне отходить, если я хочу понять, где у duck typing-а есть преимущества над ООП, а где недостатки.
AVK>Не может у него быть преимуществ над ООП. Эти понятия ортогональны.
E>>ООП базируется на трех китах: инкапсуляция, наследование и полиморфизм.
E>>Термин ООП я употребляю именно в этой интерпритации. Поэтому, если есть объект и есть методы (с полиморфизмом), но нет наследования (в принципе), то это уже не ООП.
AVK>А теперь вернемся к топику — я постоянно упоминаю о том, что наследование в рассмотрение вопроса включать не стоит. Ты же его пытаешься постоянно втащить.
Да потому, что мне это важно. Я ведь топик начал c:
Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
и тут я придерживаюсь следующего:
-- ООП без наследования, это не ООП;
-- в данном топике мне интересно противопоставление duck typing и ООП.
Но если нет наследования, то это уже не ООП и сравнивать duck typing с чем-то, что не есть ООП, я еще не пробовал.
AVK> И ты же заявляешь: AVK>
AVK>>Да нет, как раз в том и суть duck typing, что мы сужаем ОО-контракт ввиде публичного интерфейса до сигнатуры методов.
E>>Да нет, как раз в том и суть duck typing, что это уже и не ОО.
AVK>Так при чем тут, черт возьми, наследование, если я говорю о контракте? Что, duck typing отменяет понятие контракта (обрати внимание, не интерфейса, а контракта)?
Контракта не отменяет. А что такое ОО-контракт, про сужение которого ты говорил?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, AndrewVK, Вы писали:
AVK>Безусловно. Но, что характерно, в одно месте, в описании интерфейса. Все, все остальные нарушать единожды объявленный контрак не имеют права. А duck typing предлагает подобный контракт в каждом используемом классе, и все такие классы надо контроллировать.
Т.е. это нас приводит к тому, что в приложении мы должны оперировать двумя разными понятиями: интерфейс (контракт) и реализация (класс). Мы не можем создать класс без того, чтобы ранее определить его интерфейс. Т.е., если мне нужен класс "чемодан", то я сначала должен определить интерфейс "чемодан", а затем сделать его реализацию в виде класса "обычный_чемодан". Как следствие, место создания экземпляра я должен максимально локализововать (чтобы имя "обычный_чемодан" было только где-нибудь в одном месте программы), а везде, где мне нужен чемодан, я должен оперировать только интерфейсом "чемодан". Так получается?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>>> Или уже есть интерфейсы, где явно говорится, что вот этот метод не имеет побочных эффектов?
AVK>>В описании интерфейса.
E>Видимо, мы по разному понимаем понятие побочных эффектов.
Видимо ты не понял о каких эффектах шла речь. Я говорил не о том, что метод не полностью описывается контрактом, а о том, что (в полиморфном коде) нет никакой гарантии что за методом Add стоит именно то, что тебе кажется правильным.
AVK>>Ну вот о том и речь, что нужно гадать что там произошло и по какой причине.
E>Да не нужно гадать, из-за чего чемодан развалился. Гадать нужно почему на него телевизор поставили.
Для этого нужно понять что именно телевизор был причиной. А так телевизор поставили, он вроде стоит. Потом телевизор убрали. А через день он сам развалился, потому что телевизор поставили и кот нассал. И в гарантию не отнесешь, потому что в руководстве черным по белому написано — котам внутрь чемодана не ссать.
AVK>>>>А как определить что не подошла? Каков формальный критерий подходимости?
E>>>Не случилось исключения.
AVK>>Фиговый критерий, надо сказать.
E>А есть другие критерии?
В случае интерфейса критерий очень простой — интерфейс реализован. Поскольку реализация интерфейса подразумевает не просто наличие определенного набора методов с определенной сигнатурой, но и то, что эти методы делают определенные вещи.
AVK>>А как надо? Как это проконтроллировать? Опять перекладывать все на плечи программиста, т.е. юнит-тесты писать?
E>А как вообще контролировать, что программа работает? Если не брать какие-то формальные математические доказательства. Что мы делаем, чтобы убедится в том, что в ней нет ошибок?
Вариантов много. Один из — контроль соотвествия контрактов компилятором и рантаймом. В случае интерфейса формальный контракт есть, в случае duck typing нету, потому что с сигнатурой никакой семантики не связано.
AVK>>Наличие реализации интерфейса означает что класс рассчитан на использование этого интерфейса. Если класс не выполняет заявленный контракт, то это проблемы этого класса.
E>Ну выполняет он, выполняет. Этот несчастный столик является "подставкой". И если на него поставить вазу или телефон, то он справляется со своей задачей.
Если заявлена реализация интерфейса подставки, то он обязан либо работать в таковом качестве, либо выкинуть ArgumentOutOfRangeException. Потому что создатель класса знает что класс будет использован в качестве подставки.
А вот если у нас duck typing, то черт его знает, что конкретный класс, который может быть известен в рантайме, под этим будет понимать.
E>>>Но ведь, если мы знаем с объектами каких типов нам предстоит работать
AVK>>А знаем ли?
E>Кажется есть такое понятие: "локальность ссылок". Если бы не оно, программирование на динамических языках действительно представляло бы из себя сплошной глюкодром. Но ведь это не так.
Спорный вопрос
E> А не так потому, что вмешивается в дело "локальность ссылок" -- объекты используются в очень ограниченных контекстах, где легко отслеживать их типы.
Звучит круто, но совершенно непонятно. Как ты в полиморфном коде собираешься отслеживать типы? А если система компонентная?
E>Так я думаю, что пока в проекте подобная "локальность" существует, то мы действительно знаем с чем работаем.
Мне кажется, что ты подразумеваешь, что весь проект делает один человек или маленькая команда высококвалифицированных пспецов.
AVK>>А как проверить что все существующие классы с такими сигнатурами делают то же самое? Эскпериментально проверять их все? И что делать, если потом кто то другой будет добавлять новые классы, ничего не зная о твоей механике?
E>Но ведь программу перед запуском все равно придется проверять.
Вопрос в объеме таких проверок.
E>Да, это плохой критерий, я согласен с тобой. E>Но, видимо, в случае duck typing-а другого нет.
Вот поэтому duck typing и не кажется мне идеей, которую можно применять широко. В определенных случаях вполне оправданно, но только в очень определенных.
E>Может быть. Но duck typing начинает оправдываться, если у нужных нам типов нет изначально одинакового интерфейса -- ну не мы эти типы делали.
Ну ты же сам говорил — всегда можно создать обертку. Да, это сложнее, но при том и значительно надежнее. А в качестве бонуса получаем возможность подкорректировать поведение метода конкретного класса/набора классов, если оно не полностью соответствует ожидаемому. Да и не много на практике случаев, когда методы с одинаковой сигнатурой и похожей семантикой есть, а общего интерфейса нет. Так вот, навскидку, в .NET Framework я такого не припомню.
Здравствуйте, eao197, Вы писали:
E>и тут я придерживаюсь следующего: E>-- ООП без наследования, это не ООП;
Спорно.
E>-- в данном топике мне интересно противопоставление duck typing и ООП.
Ты все таки определись. Противопоставление ООП или все же противопоставление классическим ООПным контрактам — интерфейсам и базовым классам.
E>Контракта не отменяет. А что такое ОО-контракт, про сужение которого ты говорил?
Здравствуйте, AndrewVK, Вы писали:
E>>Видимо, мы по разному понимаем понятие побочных эффектов.
AVK>Видимо ты не понял о каких эффектах шла речь. Я говорил не о том, что метод не полностью описывается контрактом, а о том, что (в полиморфном коде) нет никакой гарантии что за методом Add стоит именно то, что тебе кажется правильным.
Странно, я думал, что в нашем разговоре это подразумевается.
AVK>>>Ну вот о том и речь, что нужно гадать что там произошло и по какой причине.
E>>Да не нужно гадать, из-за чего чемодан развалился. Гадать нужно почему на него телевизор поставили.
AVK>Для этого нужно понять что именно телевизор был причиной.
Так ведь в языках, в которых сильная поддержка run-time, мы об этом сразу узнаем. По stack trace.
AVK> А так телевизор поставили, он вроде стоит. Потом телевизор убрали. А через день он сам развалился, потому что телевизор поставили и кот нассал. И в гарантию не отнесешь, потому что в руководстве черным по белому написано — котам внутрь чемодана не ссать.
Извини, это другая ситуация. Мы до сих пор говорили, что телевизор поставили и опора разлетелась в пух и прах.
E>>А есть другие критерии?
AVK>В случае интерфейса критерий очень простой — интерфейс реализован. Поскольку реализация интерфейса подразумевает не просто наличие определенного набора методов с определенной сигнатурой, но и то, что эти методы делают определенные вещи.
Ты знаешь, это как расхождение теории с практикой. В теории (интерфейсы + проверка компилятора) все OK. На практике -- через несколько часов работы приложение падает.
AVK>Вариантов много. Один из — контроль соотвествия контрактов компилятором и рантаймом. В случае интерфейса формальный контракт есть, в случае duck typing нету, потому что с сигнатурой никакой семантики не связано.
Опять же, только реальные запуски могут подтвердить правильность программы.
AVK>>>Наличие реализации интерфейса означает что класс рассчитан на использование этого интерфейса. Если класс не выполняет заявленный контракт, то это проблемы этого класса.
E>>Ну выполняет он, выполняет. Этот несчастный столик является "подставкой". И если на него поставить вазу или телефон, то он справляется со своей задачей.
AVK>Если заявлена реализация интерфейса подставки, то он обязан либо работать в таковом качестве, либо выкинуть ArgumentOutOfRangeException.
Именно об этом я до сих пор и говорил. Выкинуто такое исключение.
Только вот его тот, кто использует данный класс подставки, не ждал.
E>> А не так потому, что вмешивается в дело "локальность ссылок" -- объекты используются в очень ограниченных контекстах, где легко отслеживать их типы.
AVK>Звучит круто, но совершенно непонятно.
Это кстати, одно из самых неожиданных открытий, которые делаешь, когда начинаешь программировать на динамических языках. Непонятно почему, но ошибок оказывается гораздо меньше, чем ожидал.
AVK> Как ты в полиморфном коде собираешься отслеживать типы?
А зачем? Все типы run-time отслеживает. Я ведь говорю про строгую типизацию (как в Ruby/Python/Smalltalk).
AVK> А если система компонентная?
Вот не знаю. Есть у меня подозрение, что не все будет ладно в датском королевстве.
E>>Так я думаю, что пока в проекте подобная "локальность" существует, то мы действительно знаем с чем работаем.
AVK>Мне кажется, что ты подразумеваешь, что весь проект делает один человек или маленькая команда высококвалифицированных пспецов.
Про высококвалифицированных спецов не могу сказать. Пока я только про свой опыт сужу.
E>>Да, это плохой критерий, я согласен с тобой. E>>Но, видимо, в случае duck typing-а другого нет.
AVK>Вот поэтому duck typing и не кажется мне идеей, которую можно применять широко. В определенных случаях вполне оправданно, но только в очень определенных.
Хотя кто его знает. У меня еще не было опыта проектирования больших систем сразу в терминах duck typing.
AVK>Да и не много на практике случаев, когда методы с одинаковой сигнатурой и похожей семантикой есть, а общего интерфейса нет. Так вот, навскидку, в .NET Framework я такого не припомню.
Имхо, .NET Framework -- это не очень хороший для данной темы пример. Все же в нем повторно реализованы многократно пройденные вещи.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, AndrewVK, Вы писали:
E>>и тут я придерживаюсь следующего: E>>-- ООП без наследования, это не ООП;
AVK>Спорно.
E>>-- в данном топике мне интересно противопоставление duck typing и ООП.
AVK>Ты все таки определись. Противопоставление ООП или все же противопоставление классическим ООПным контрактам — интерфейсам и базовым классам.
Fundamental concepts of Object Oriented Programming
Object-oriented programming emphasizes the following concepts:
* Objects — Packaging data and functionality together into units within a running computer program; objects are the basis of modularity and structure in an object-oriented computer program. Objects are self contained and should be easily identifiable. This modularity should allow the program parts to correspond to aspects of the problem.
* Abstraction — The ability for a program to ignore some aspects of the information that it is manipulating, i.e. the ability to focus on the essential. Each object in the system serves as a model of an abstract "actor" that can perform work, report on and change its state, and "communicate" with other objects in the system, without revealing how these features are implemented. Processes, functions or methods may also be so abstracted, and when they are, a variety of techniques are required to extend an abstraction.
* Encapsulation — Ensures that users of an object cannot change the internal state of the object in unexpected ways; only the object's own internal methods are allowed to access its state. Each object exposes an interface that specifies how other objects may interact with it. Other objects will rely exclusively on the object's external interface to interact with it.
* Polymorphism via message sending. Instead of subroutine calls, object-oriented languages can make message sends; the specific method which responds to a message send depends on what specific object the message is sent to. For example, if a bird receives the message "move fast", it will flap its wings and fly. If a lion receives the same message, it will move its legs and run. Both answer the same request, but in ways appropriate to each creature. This gains polymorphism, because a single variable in the program text can hold different kinds of objects as the program runs, and thus the same program text can invoke different methods at different times in the same execution. To contrast, functional languages gain polymorphism through the use of first-class functions.
* Inheritance- Organizes and facilitates polymorphism and encapsulation by permitting objects to be defined and created that are specialized types of already-existing objects — these can share (and extend) their behavior without having to reimplement that behavior. (Object-based languages do not always have inheritance.)
Как-то без наследования я себе ООП не представляю.
E>>Контракта не отменяет. А что такое ОО-контракт, про сужение которого ты говорил?
AVK>Я говорил про сужение просто контракта.
Ok.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
E>>ООП базируется на трех китах: инкапсуляция, наследование и полиморфизм.
E>>Термин ООП я употребляю именно в этой интерпритации. Поэтому, если есть объект и есть методы (с полиморфизмом), но нет наследования (в принципе), то это уже не ООП.
D>Вообще-то ООП это объекты, обменивающиеся сообщениями. Отсюда следуют только инкапуляция и полиморфизм — потому как утверждается, что объекту сообщение послать можно, а как он на него будет реагировать это его дело. Наследование же появляется не во всех ООЯ, а только в (хотя бы динамически) типизированных.
eao197 wrote:
> Т.е. это нас приводит к тому, что в приложении мы должны оперировать > двумя разными понятиями: интерфейс (контракт) и реализация (класс). Мы > не можем создать класс без того, чтобы ранее определить его интерфейс.
Поправка: сейчас создание класса неявно создает несколько интерфейсов,
совпадающих с набором публичных/защищенных/приватных методов в классе.
> Т.е., если мне нужен класс "чемодан", то я сначала должен определить > интерфейс "чемодан", а затем сделать его реализацию в виде класса > "обычный_чемодан". Как следствие, место создания экземпляра я должен > максимально локализововать (чтобы имя "обычный_чемодан" было только > где-нибудь в одном месте программы), а везде, где мне нужен чемодан, я > должен оперировать только интерфейсом "чемодан". Так получается?
Здравствуйте, Sinclair, Вы писали:
E>>Потому, что мы получили это исключение в результате действия "поставить чемодан на опору". А если бы соседский кот туда не нассал, может и выдержал бы, а так -- подгнил чуточку. S>ДА какое исключение? Речь идет о том, что ты получишь compile-time error! Потому, что чемодан не работает как подставка. Точка.
Совсем не точка. Мы с AndrewVK говорили об объектах, которые поддерживают несколько интерфейсов. В частности, чемодан поддерживает: "контейнер для ручной клади", "хранилище хлама", "подставка". Речь шла о том, что если чемодан как подставку использовать для не подходящей вещи, то он сломается, а мы поимеем проблемы. На что я возразил, что интерфейс "подставка", реализованный СтекляннымЖурнальнымСтоликом для того же самого телефизора так же не подойдет, получится исключения. Поэтому все compile-time проверки в этом случае идут лесом.
S>Вообще, я вижу у тебя отсутствие понимания двух вещей: S>1. Наследование и реализация интерфейсов — разные вещи. То, что в C++ это выглядит одинаково, лучше считать случайным совпадением. Рекомендую как можно быстрее освоить разницу между этими понятиями. Иначе твои представления об ООП не будут соответствовать современному уровню.
Ok.
Интересная, однако, тенденция. Первоначально ООП вообще не подразумевало наследования (Re[3]: Снова о типизации :)
), затем появились постулаты инкапсуляция/полиморфизм/наследование, теперь сюда же еще и интерфейсы добавляются. Имхо, если понятие такое аморфное и видоизменяется со временем, то это отнюдь не строгое понятие. А может и не понятие вовсе.
S>2. Помимо наличия самой ошибки, важны три фактора: S>- последствия. Хуже всего: недетерминированное поведение в продакшн системе. Лучше всего: запись в лог и выполнение корректной fallback стратегии S>- место проявления. Хуже всего: ошибка обнаруживается неопределенно далеко от того места, где она сделана. Лучше всего: ошибка обнаруживается непосредственно там, где ее можно исправить S>- время проявления. Хуже всего: ошибка проявляется иногда, при трудновоспроизводимом стечении обстоятельств. Лучше всего: есть детерминированный момент, когда ошибка, если есть, заведомо себя проявит.
S>Так вот, при дак тайпинге ошибка несоответствия семантики расположена в самом темном углу этого кубика. Потому, что S>а) совершенно неизвестно, к чему она приведет. Метод "крякни" может спокойно сделать свою работу. И только после расследования и суда нам станет известно, что "крякнуть" означало совсем не то. S>б) проявляется она вовсе не там, где мы сохранили ссылку на псевдоутку. А, самое раннее, при вызове метода "крякни". S>в) момент проявления совершенно неопределен.
Все перечисленные тобой проблемы постоянно проявляются и в программах со статической типизацией. В которых совершенно нет duck typing-а.
S>При использовании настоящих интерфейсов, мы либо получим ошибкупри компиляции и именно в том месте, где мы присваиваем хакера в слот для утки, либо в рантайме — в том же месте. При этом в рантайме мы получим детерминированное поведение, которое не приведет нас в тюрьму.
Все это было бы так, если бы программы на языках со статической и сильной типизацией работали бы стабильно и без ошибок. Но ошибки есть. Как те, что проявляются сразу в момент возникновения каких-то проблем, так и наведенные, проявлюящиеся спустя длительное время.
А в том, что ты говоришь, есть изрядная доля веры в компилятор и статическую типизацию. На первый взгляд кажется, что статическая типизация -- это панацея от большого класса ошибок. Но, с другой стороны, статическая типизация требует от разработчика больших усилий при проектировании и реализации.
А что в итоге: ошибки в ПО все равно есть. И для потребителя программ это главное. Ему без разницы, статическая там типизация или динамическая. Раз программа упала, значит недотестировали. Или условия задачи не поняли. Или реализация со всеми ограничениями наследования/интерфейсов оказалась настолько запутанной, что в поведении программы вообще мало кто разбирается.
Статическая типизации и "современный" ООП применяются повсемесно. Но повышения надежности программ и сокращения сроков разработки не происходит. Duck typing применяется гораздо реже, многие из моих оппонетнов в данном топике, вероятно, вообще его не применяют. Но при этом пытаются доказать, что duck typing менее надежен, чем статическая типизация и интерфейсы. Может быть вы и правы.
Хотя, с другой стороны, может быть duck typing позволяет тратить меньше времени на проектирование/разработку, а освободившееся время посвящать более тчательному и всестороннему тестированию? Может быть здесь сработает тот же принцип, что и при программировании на динамических языках: ошибок гораздо меньше, чем предполагаешь в начале. А из-за чего -- точно не понятно. Может быть потому, что больше тестируешь. Может быть потому, что код получается компактным. Может быть потому, что меньшим количеством сущностей оперировать нужно. Много мелких факторов, которые складываются и дают совершенно неожиданный для тебя результат.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
AVK>>Видимо ты не понял о каких эффектах шла речь. Я говорил не о том, что метод не полностью описывается контрактом, а о том, что (в полиморфном коде) нет никакой гарантии что за методом Add стоит именно то, что тебе кажется правильным.
E>Странно, я думал, что в нашем разговоре это подразумевается.
При чем тут наш разговор? Я о том, что, когда я разрабатываю свой класс, я вовсе не обязан придерживаться твоих представлений о том, что должен делать метод Add, хотя бы потому что я о твоих представлениях ничего не знаю.
AVK>>Для этого нужно понять что именно телевизор был причиной.
E>Так ведь в языках, в которых сильная поддержка run-time, мы об этом сразу узнаем. По stack trace.
Это если эффект будет мгновенным. Но, о чем я и писал, эффект может быть и отложенным, поскольку, в отличие от метода интерфейса (для которого обычно ясна смысловая составляющая на момент его разработки), произвольный метод просто может сделать чуточку больше чем тебе нужно.
AVK>>В случае интерфейса критерий очень простой — интерфейс реализован. Поскольку реализация интерфейса подразумевает не просто наличие определенного набора методов с определенной сигнатурой, но и то, что эти методы делают определенные вещи.
E>Ты знаешь, это как расхождение теории с практикой. В теории (интерфейсы + проверка компилятора) все OK. На практике -- через несколько часов работы приложение падает.
Это некорректный прием спора.
AVK>>Вариантов много. Один из — контроль соотвествия контрактов компилятором и рантаймом. В случае интерфейса формальный контракт есть, в случае duck typing нету, потому что с сигнатурой никакой семантики не связано.
E>Опять же, только реальные запуски могут подтвердить правильность программы.
Тем не менее в случае явно задекларированного интерфейса вероятность несоответствия существенно больше.
AVK>>Если заявлена реализация интерфейса подставки, то он обязан либо работать в таковом качестве, либо выкинуть ArgumentOutOfRangeException.
E>Именно об этом я до сих пор и говорил. Выкинуто такое исключение. E>Только вот его тот, кто использует данный класс подставки, не ждал.
А исключение тем и отличается от кодов возврата, что его ждать необязательно.
E>>> А не так потому, что вмешивается в дело "локальность ссылок" -- объекты используются в очень ограниченных контекстах, где легко отслеживать их типы.
AVK>>Звучит круто, но совершенно непонятно.
E>Это кстати, одно из самых неожиданных открытий, которые делаешь, когда начинаешь программировать на динамических языках. Непонятно почему, но ошибок оказывается гораздо меньше, чем ожидал.
Ты мне так рассказываешь, как будто я никогда этого не делал.
AVK>> А если система компонентная?
E>Вот не знаю. Есть у меня подозрение, что не все будет ладно в датском королевстве.
А у меня скорее уверенность (при широкомасштабном применении).
E>>>Так я думаю, что пока в проекте подобная "локальность" существует, то мы действительно знаем с чем работаем.
AVK>>Мне кажется, что ты подразумеваешь, что весь проект делает один человек или маленькая команда высококвалифицированных пспецов.
E>Про высококвалифицированных спецов не могу сказать. Пока я только про свой опыт сужу.
Т.е. то, что реальный софт пишут команды, ты в своей оценке не учитываешь?
AVK>>Да и не много на практике случаев, когда методы с одинаковой сигнатурой и похожей семантикой есть, а общего интерфейса нет. Так вот, навскидку, в .NET Framework я такого не припомню.
E>Имхо, .NET Framework -- это не очень хороший для данной темы пример. Все же в нем повторно реализованы многократно пройденные вещи.
Говори уж прямо — не очень хороший для твоей точки зрения. А в качестве примера типичной современной библиотеки он очень хорош. Можешь еще джавовскую либу поглядеть, но только там еще веселее будет, Sun, в отличие от MS, умеет применять интерфейсы широко и с умом, особенно в свежем коде.
Здравствуйте, Sinclair, Вы писали:
E>>Совсем не точка. Мы с AndrewVK говорили об объектах, которые поддерживают несколько интерфейсов. В частности, чемодан поддерживает: "контейнер для ручной клади", "хранилище хлама", "подставка". S>Нет. Речь шла о duck vs normal тайпинге.
Имхо, duck typing позволяет при проектировании и программировании придерживаться других правил: правил "похожести". Если какая-то сущность похожа на подставку, давайте будем использовать ее как подставку. Если похожа на хранилище ненужного хлама -- забъем ее под завязку. В этом, имхо, и разница между ООП и duck typing-ом. В ООП мы описываем похожести в виде интерфейсов и требуем их оформления и наследования. В duck typing-е мы просто находим похожести и используем их затем без наследования. Интерфейсы и там, и там существуют. Но подход к ним разный. Похожесть, например, в Ruby определяется не по наследованиию от чего-нибудь, а по наличию метода, реализующего нужную нам операцию. Поэтому, имхо, при проектировании в рамках duck typing-а мы просто-напросто опускаем такую часть, как построение иерархии наследования.
Затем нам в программе потребовались подставки, для чего мы делаем интерфейс:
class Pedestal
{
public :
virtual void put_on_top( Weight weight ) = 0;
};
[/code]
Реализуем этот интервейс в разных классах, в том числе и в злополучном СтеклянномЖурнальномСтолике:
[ccode]
class Glass_Magazine_Table : public Pedestal
{
public :
virtual void put_on_top( Weight weight )
{
if( weight > Weight::kg( 10 ) )
throw Invalid_Weight();
...
}
};
И пишем какой-то метод:
void unpack_tv_and_put_on( Tv_Box & tv_box, Pedestal & pedestal )
{
Tv & tv = tv_box.unpack();
pedestal.put_on_top( tv.weight() );
}
Через какое-то время нам приходится использовать в качестве подставки чемодан. Просто так в unpack_tv_and_put_on его не передашь. Делаем вокруг чемодана обертку:
class Suitcase_As_Pedestal : public Pedestal
{
public :
Suitcase_As_Pedestal( Suitcase & s ) : s_( s ) {}
virtual void put_on_top( Weight weight ) { s_.put_on_top( weight ); }
};
После чего компилятор нам дает добро на написание кода:
И что? Надежность такого решения повысилась?
Да ничего подобного. Если у какого-то чемодана предел веса высокий, в на него будут ставить маленькие телевизоры, то этот код будет работать и работать. До первого слишком тяжелого телевизора. Тоже самое и со стеклянными столиками.
Проблема здесь в том, что мы слишком надеемся на интерфейсы и не думаем об обработке ошибок. А если мы должны заботится о том, что операция put_on_top может привести к краху "подставки", то фокусы с обертками превращаются всего лишь в обман компилятора. На который нужно тратить время и силы. Например, вот так:
class Suitcase_As_Pedestal : public Pedestal
{
public :
Suitcase_As_Pedestal( Suitcase & s ) : s_( s ) {}
virtual void put_on_top( Weight weight )
{
try
{
s_.put_on_top( weight );
}
catch( const Suitcase_Absolutly_Crashed & )
{
// Должны породить то, что якобы ожидают.throw Invalid_Weight();
}
// Надеемся, что в Suitcase не будут появляться новые
// исключения со временем.
}
};
Хотя можно было бы сделать просто:
template< class T >
void unpack_tv_and_put_on( Tv_Box & tv_box, T & pedestal )
{
Tv & tv = tv_box.unpack();
pedestal.put_on_top( tv.weight() );
}
и получить в точности то же самое. Так зачем платить больше, если результат одинаковый?
Самое важное возражение тут уже приводилось не раз: при развитии проекта не специфицированные формальным образом (т.е. средствами языка в коде) соглашения об интерфесах станут кошмаром в большом проекте и при сопровождении.
Это пока железобетонный аргумент, против которого я не знаю как возразить. Есть только пара замечаний (как будто я говорю в оправдание и шепотом):
— если код хорошо покрыт unit-тестингами, то нарушение кем-то интерфейса сразу отлавливается;
— все не так страшно, поскольку в приложении не так уж и много глобальных интерфейсов, реализованных сразу во всех частях приложения
E>>Речь шла о том, что если чемодан как подставку использовать для не подходящей вещи, то он сломается, а мы поимеем проблемы. На что я возразил, что интерфейс "подставка", реализованный СтекляннымЖурнальнымСтоликом для того же самого телефизора так же не подойдет, получится исключения. Поэтому все compile-time проверки в этом случае идут лесом. S>Не идут они никаким лесом. Потому, что стратегия реакции на постановку чего-либо является частью семантики интерфейса подставки. У нее есть детерминированный способ отреагировать на телевизор — выкинуть ArgumentException или InvalidOperationException. Это в корне отличается от ситуации, когда мы просто передаем наш телевизор в случайно попавшийся метод "поставить". А этот метод, в свою очередь, может попытаться выполнить над поставленной на него вещью какую-то не ту операцию. И т.д.
Если мы по дурости создали обертку, реализующую наш интерфес над неподходящим классом, то мы получим тот же самый эффект. Имхо, на уровне проектирования нужно думать, зачем нам потребовалось ставить телевизор на чемодан. А случайная передача чемодана в качестве подставки в языке с динамической типизацией -- это редкость, да и помноженная на вероятность того, что у чемодана окажется такой же метод put_on_top, как и у подставки. Да и отлавливаться это тестами должно. Поскольку даже в языке со статической типизацией, все равно нужно будет проверять, выдержит ли программа установку слишком больших грузов на слишком хилые подставки.
Так что статическая типизация и интерфейсы не защищают нас от преднамеренного использования неподходящих объектов в несвойственных им ролях.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Т.е. это нас приводит к тому, что в приложении мы должны оперировать двумя разными понятиями: интерфейс (контракт) и реализация (класс). Мы не можем создать класс без того, чтобы ранее определить его интерфейс. Т.е., если мне нужен класс "чемодан", то я сначала должен определить интерфейс "чемодан", а затем сделать его реализацию в виде класса "обычный_чемодан". Как следствие, место создания экземпляра я должен максимально локализововать (чтобы имя "обычный_чемодан" было только где-нибудь в одном месте программы), а везде, где мне нужен чемодан, я должен оперировать только интерфейсом "чемодан". Так получается?
Немного не так. Во-первых — создание интерфейса чемодан имеет смысл только если есть некий внешний по отношению к чемоданам код, который все чемоданы (и только чемоданы) обрабатывает по единым правилам. Елси же такого кода нет, то и интерфейс вводить не нужно. Интерфейс должен отражать некий аспект (обычно интерфейс это прилагательное, в отличие от класса — существительного) — например "носимый", "хранимый", "хранящий" и т.п.
Здравствуйте, AndrewVK, Вы писали:
AVK>При чем тут наш разговор? Я о том, что, когда я разрабатываю свой класс, я вовсе не обязан придерживаться твоих представлений о том, что должен делать метод Add, хотя бы потому что я о твоих представлениях ничего не знаю.
А я о том, что когда я начинаю использовать твой класс, я понимаю, что делает метод Add в твоем классе.
E>>Опять же, только реальные запуски могут подтвердить правильность программы.
AVK>Тем не менее в случае явно задекларированного интерфейса вероятность несоответствия существенно больше.
.
E>>Вот не знаю. Есть у меня подозрение, что не все будет ладно в датском королевстве.
AVK>А у меня скорее уверенность (при широкомасштабном применении).
Вот это существенно.
E>>Про высококвалифицированных спецов не могу сказать. Пока я только про свой опыт сужу.
AVK>Т.е. то, что реальный софт пишут команды, ты в своей оценке не учитываешь?
Блин, да я же не агитирую всех за тотальное использование duck typing-а. Я хочу понять границы его применимости.
Командная разработка, действительно, вряд ли выиграет от использования duck typing-а. Это очень серьезный минус для duck typing-а.
E>>Имхо, .NET Framework -- это не очень хороший для данной темы пример. Все же в нем повторно реализованы многократно пройденные вещи.
AVK>Говори уж прямо — не очень хороший для твоей точки зрения. А в качестве примера типичной современной библиотеки он очень хорош.
В качестве универсальной системной библиотеки, не спорю.
Только, имхо, duck typing показывает свои преимущества при написании прикладного кода, который интегрирует в себе совершенно разные библиотеки. Вот потребуется кому-то читать данные как из файла, так из сокета, так из какого-то хитрого устройства ввода-вывода. Средства для чтения из сокета и файла он возьмет из .NET Framework. Для для устройства есть готовая либа, которая сделана совершенно не в духе .NET. Как сделать код чтения унифицированным? Написать интерфейс над библиотекой устройства. Разница в том, как этот интерфейс будет выглядеть в случае формализованных интерфейсов и duck typing-а. В некоторых случаях duck typing менее геморройный.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>А я о том, что когда я начинаю использовать твой класс, я понимаю, что делает метод Add в твоем классе.
Разница только в том, что, в полиморфных алгоритмах, интерфейс один, а методов (каждый со своей семантикой) ровно столько, сколько теоретически в этот алгоритм может попасть классов.
AVK>>Т.е. то, что реальный софт пишут команды, ты в своей оценке не учитываешь?
E>Блин, да я же не агитирую всех за тотальное использование duck typing-а. Я хочу понять границы его применимости.
ИМХО затычки и заплатки. Либо какая то сверхуниверсальная механика.
E>Только, имхо, duck typing показывает свои преимущества при написании прикладного кода, который интегрирует в себе совершенно разные библиотеки.
Какие?
E> Вот потребуется кому-то читать данные как из файла,
System.IO
E> так из сокета,
System.Net
E> так из какого-то хитрого устройства ввода-вывода.
System.IO.Ports
E>Для для устройства есть готовая либа, которая сделана совершенно не в духе .NET.
Здравствуйте, AndrewVK, Вы писали:
AVK>Разница только в том, что, в полиморфных алгоритмах, интерфейс один, а методов (каждый со своей семантикой) ровно столько, сколько теоретически в этот алгоритм может попасть классов.
В том-то и дело, что теоритически. А если делаешь решение конкретной задачи, то количество классов, с которыми приходится работать, очень ограниченное.
E>>Блин, да я же не агитирую всех за тотальное использование duck typing-а. Я хочу понять границы его применимости.
AVK>ИМХО затычки и заплатки. Либо какая то сверхуниверсальная механика.
Пока очень похоже на то. Т.е. не полноценная парадигма, а один из способов обобщенного программирования.
AVK>Пример такой либы в студию.
Здравствуйте, eao197, Вы писали:
E>В том-то и дело, что теоритически. А если делаешь решение конкретной задачи, то количество классов, с которыми приходится работать, очень ограниченное.
Но всегда >1, иначе просто смысла нет. Следовательно даже в самых удачных случаях duck typing значительно тяжелее контроллировать.
AVK>>Пример такой либы в студию.
E>Это я с ходу нафантазировал.
Ну вот а я за все время работы с разнообразнейшими либами ни разу такой потребности не ощущал.
Здравствуйте, AndrewVK, Вы писали:
E>>В том-то и дело, что теоритически. А если делаешь решение конкретной задачи, то количество классов, с которыми приходится работать, очень ограниченное.
AVK>Но всегда >1, иначе просто смысла нет. Следовательно даже в самых удачных случаях duck typing значительно тяжелее контроллировать.
На C++ных шаблонах такого не замечал.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
AndrewVK,
> E> Если duck typing встраивается в статически типизированный язык, то получаем те же самые проверки. > > Нет, не получаем, потому что нет формального контракта, связанного с сигнатурой. Совпадение сигнатуры еще ничего не означает. А если мы такой формальный контракт введем, то получим интерфейсы — вид сбоку.
Если речь идет о concepts, то совсем не интерфейсы в смысле C++/C#/Java: указать, что данный тип удовлетворяет заданным ограничениям можно при использовании типа, а не при его определении.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
>> Нет, не получаем, потому что нет формального контракта, связанного с сигнатурой. Совпадение сигнатуры еще ничего не означает. А если мы такой формальный контракт введем, то получим интерфейсы — вид сбоку.
ПК>Если речь идет о concepts, то совсем не интерфейсы в смысле C++/C#/Java: указать, что данный тип удовлетворяет заданным ограничениям можно при использовании типа, а не при его определении.
А казалось бы, при чем тут С++?
Нет, речь не о concepts
... << RSDN@Home 1.2.0 alpha rev. 615 on Windows XP 5.1.2600.131072>>
Здравствуйте, Павел Кузнецов, Вы писали:
>> AVK>Т.е. то, что реальный софт пишут команды, ты в своей оценке не учитываешь? >> >> Блин, да я же не агитирую всех за тотальное использование duck typing-а. Я хочу понять границы его применимости. >> Командная разработка, действительно, вряд ли выиграет от использования duck typing-а. Это очень серьезный минус для duck typing-а.
ПК>Зависит от конкретной команды и конкретной задачи. Structural/Latent/Duck typing -- инструмент, вполне полезный и при командной разработке. Использование Smalltalk, Lisp и шаблонов C++ вполне это подтверждает.
Согласен. Но здесь мы с AndrewVK говорили, как мне кажется, о среднестатистических командах.
Кстати, о конкретных командах и конкретных задачах подтвердает и то, что многие проекты на этих языках делаются очень небольшими, сплоченными коллективами, или вообще одиночками.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>здесь возникает философский вопрос: а кто должен знать нужный способ? Мне (пока) наиболее изящным и, скажем так, прямым решением кажется концепция traits: ЗХ>
ЗХ>Здесь и интерфейс stream не раздувается, но и решение принимается "тем, кто знает как решать". ЗХ>Грубо говоря, если появится еще один тип потока (предположенный мной), у которого есть функция seek, но смысл ее другой, а сдвигаться вперед надо функцией move — то нужно всего лишь описать traits для типа этого потока, не меняя клиентский код.
А может проще дать пользователю библиотеки просто функцию передать в качестве параметра? Будет в принципе тот же traits:
и никаких расширений языка не потребуется.
В общем, если интерфейс заранее известен, то просто ползуемся ООП. Если нет, то применяем функциональный подход. В итоге все красиво, быстро и без утиных историй.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Если речь идет о concepts, то совсем не интерфейсы в смысле C++/C#/Java: указать, что данный тип удовлетворяет заданным ограничениям можно при использовании типа, а не при его определении.
Паш, concepts вводятся в С++ (если конечно вводятся), чтобы уменьшить последствия утиной типизации присутствующей в шаблонах сегодня. Причем средство это не обязательное, и когда все осознают его необходимость баАальшой вопрос.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
E>ООП базируется на трех китах: инкапсуляция, наследование и полиморфизм.
От именно. И добиваясь полиморфности так где не надо ты ломашь не только принцип наследования, но и инкапсуляции, так как допускаешь обращение к данным в обход контракта класса.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
>> Исторически статическая типизаия использовалась как способ дать компилятору подскази для построения эффективного кода. Например, С++ компилятор, строит vtbl для класса размером не на все возможные (в системе) сообщения, а только на определенные в этом классе. В Smalltalk, например, такой оптимизации не делается. По этому если Smalltalk объект получает "непонятное" для него сообщение, то он кидает исключение, а C++ объект начинает вести себя непредсказуемым образом (что почти всегда приводит к GPF, а иногда к порче данных или странному поведению).
ПК>Можно пояснить мысль поподробнее относительно того, что ты понимаешь под передачей объекту C++ "непонятного" для него сообщения?
Наверно нечто вроде:
int i = 0;
double* pd = (double*)&i;
*pd = 1.3;
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, AndrewVK, Вы писали:
AVK>В С# 3.0 duck typing в нескольких местах есть. Например сокращенная инициализация массива: AVK>
AVK>IList<Bird> birds = new List<Bird> {penguin, eagle, hedgehog};
AVK>
AVK>Преобразуется компилятором в код: AVK>
AVK>List<Bird> l = new List<Bird>();
AVK>l.Add(penguin);
AVK>l.Add(eagle);
AVK>l.Add(hedgehog);
AVK>IList<Bird> birds = l;
AVK>
AVK>Так вот, метод Add подцепляется через тот самый duck typing. Почему так? Очень просто. С одной стороны нельзя привязываться к конкретным существующим интерфейсам, потому что фича сверхуниверсальная, с другой нельзя вводить свой, потому что тогда существующие коллекции не будут работать, с третьей стороны необходимо поддержать extension methods, которые принципиально не могут реализовывать интерфейсы. Но это мера вынужденная, а не некий универсальный принцип.
Серьезно? А мужики то и не знают поди. Я вот тоже попробовал написать вот так:
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
A a = new A { 1, 2, 3 };
}
}
class A
{
public void Add(int item)
{
Console.WriteLine(item);
}
}
И мне рассказали, что:
Program.cs(8,20): error CS1801: The member initializer does not have an identifiable name
CSC : fatal error CS0001: Internal compiler error (0x80004005)
Program.cs(8,23): error CS1801: The member initializer does not have an identifiable name
CSC : fatal error CS0001: Internal compiler error (0x80004005)
Done building project "LINQConsoleApplication1.csproj" -- FAILED.
========== Build: 0 succeeded or up-to-date, 1 failed, 0 skipped ==========
А вот когда интерфейсик ICollection<int> реализовал:
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
A a = new A { 1, 2, 3 };
}
}
class A : ICollection<int>
{
#region ICollection<int> Members
public void Add(int item)
{
Console.WriteLine(item);
}
public void Clear()
{
throw new Exception("The method or operation is not implemented.");
}
public bool Contains(int item)
{
throw new Exception("The method or operation is not implemented.");
}
public void CopyTo(int[] array, int arrayIndex)
{
throw new Exception("The method or operation is not implemented.");
}
public int Count
{
get { throw new Exception("The method or operation is not implemented."); }
}
public bool IsReadOnly
{
get { throw new Exception("The method or operation is not implemented."); }
}
public bool Remove(int item)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
#region IEnumerable<int> Members
public IEnumerator<int> GetEnumerator()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
...то сразу дали:
1
2
3
Доктор! Что я делаю не так?
Короче, слава богу такой фигни как утиные истории в шарпе все же стараются избегать.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Возможно, это и правильно, что конструктор колекции требует публичного метода Add реализующего ICollection<T>.Add(T).
Если метод ICollection<T>.Add(T) скрыт, велика вероятность того, что колекция не допускает модификации.
Если бы привязка шла к явной реализации интерфейса, результат выполнения
var carvedinstone = new ReadOnlyCollection { 1, 2, 3 };
Здравствуйте, VladD2, Вы писали:
VD>А может проще дать пользователю библиотеки просто функцию передать в качестве параметра? Будет в принципе тот же traits: VD>
Нет, в этом случае пользователю load_header_and_data нужно знать о том, что внутри используется skip_data. А от этого пользователя load_header_and_data хотелось бы избавить -- это не его проблемы. Так что вариант с утиными историями или traits-ами гораздо удобнее.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Нет, в этом случае пользователю load_header_and_data нужно знать о том, что внутри используется skip_data. А от этого пользователя load_header_and_data хотелось бы избавить -- это не его проблемы. Так что вариант с утиными историями или traits-ами гораздо удобнее.
Ему и так нужно знать. Только тут хотя бы он может задать то что хочет, а не полагаться на соотвествие имен.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
не "ничего не делает", а проверяет соответствие аргументов шаблона некоторым требованиям. И да, такой код вполне согласуется с понятиями хорошего стиля более, чем у одной команды. Для меня лично польза правил, запрещающих писать код, помогающий отлавливать ошибки на более ранних стадиях, весьма сомнительна.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
eao197,
> ПК> Зависит от конкретной команды и конкретной задачи. Structural/Latent/Duck typing -- инструмент, вполне полезный и при командной разработке. Использование Smalltalk, Lisp и шаблонов C++ вполне это подтверждает.
> Согласен. Но здесь мы с AndrewVK говорили, как мне кажется, о среднестатистических командах.
Средняя температура по больнице?
> Кстати, о конкретных командах и конкретных задачах подтвердает и то, что многие проекты на этих языках делаются очень небольшими, сплоченными коллективами, или вообще одиночками.
Имхо, очень по-разному... Во всяком случае, применительно к Си++ уж точно. О Smalltalk, Python, Ruby, Lisp etc. мне говорить сложно: в командной работе на этих языках я не участвовал.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
VladD2,
> ПК>Если речь идет о concepts, то совсем не интерфейсы в смысле C++/C#/Java: указать, что данный тип удовлетворяет заданным ограничениям можно при использовании типа, а не при его определении.
> Паш, concepts вводятся в С++ (если конечно вводятся), чтобы уменьшить последствия утиной типизации присутствующей в шаблонах сегодня. Причем средство это не обязательное, и когда все осознают его необходимость баАальшой вопрос.
Это инструмент, нужный для определенных случаев. То, что их больше, не означает, что структурная типизация без ограничений не нужна. Нужно и то, и другое. И совершенно верно вводятся concepts, не запрещая структурной типизации в чистом виде. То чего, к сожалению, сделать уже нельзя, так это изменить выбор по умолчанию.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
AndrewVK,
>>> Нет, не получаем, потому что нет формального контракта, связанного с сигнатурой. Совпадение сигнатуры еще ничего не означает. А если мы такой формальный контракт введем, то получим интерфейсы — вид сбоку.
> ПК> Если речь идет о concepts, то совсем не интерфейсы в смысле C++/C#/Java: указать, что данный тип удовлетворяет заданным ограничениям можно при использовании типа, а не при его определении.
> А казалось бы, при чем тут С++?
При том, что это конкретный язык со статической структурной типизацией вместо сфероконя.
> Нет, речь не о concepts
Тогда, пожалуйста, поясни свою мысль. Я привел concepts как пример формального контракта, связанного с сигнатурой, и указал разницу с интерфейсами, которую ты отрицал в процитированной выше цитате.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
VladD2,
> ПК>Можно пояснить мысль поподробнее относительно того, что ты понимаешь под передачей объекту C++ "непонятного" для него сообщения? > > Наверно нечто вроде: >
> int i = 0;
> double* pd = (double*)&i;
> *pd = 1.3;
>
Что, очевидно, таковым не является, и, более того, запрещено спецификациями языков Си и Си++ (хотя среда исполнения и не обязана детектировать нарушения).
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>При том, что это конкретный язык со статической структурной типизацией вместо сфероконя.
Я говорил о принципах, а не о конкретном языке.
>> Нет, речь не о concepts
ПК>Тогда, пожалуйста, поясни свою мысль. Я привел concepts как пример формального контракта, связанного с сигнатурой, и указал разницу с интерфейсами, которую ты отрицал в процитированной выше цитате.
В той цитате я говорил о конкретном duck typing, описанном Евгением (я так понимаю в реализации Ruby). Что касается concepts, то не готов говорить о них за недостаточным знанием предмета.
... << RSDN@Home 1.2.0 alpha rev. 615 on Windows XP 5.1.2600.131072>>
Здравствуйте, Павел Кузнецов, Вы писали:
>> Согласен. Но здесь мы с AndrewVK говорили, как мне кажется, о среднестатистических командах.
ПК>Средняя температура по больнице?
Угу
Если пытаться продвигать чего-нибудь массовое (или в массы ), то нужно именно на среднюю температуру ориентироваться.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Это инструмент, нужный для определенных случаев. То, что их больше, не означает, что структурная типизация без ограничений не нужна. Нужно и то, и другое. И совершенно верно вводятся concepts, не запрещая структурной типизации в чистом виде. То чего, к сожалению, сделать уже нельзя, так это изменить выбор по умолчанию.
Думаю, ты просто плывешь по течению принимая за правильное складывающееся положение вещей. В общем, я с тобой не согласен. Я считаю, что будь коцепты введены в самом начали и не позволяли бы писать без них, то С++ стал чуточку проще в использовании.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
ПК>>Можно пояснить мысль поподробнее относительно того, что ты понимаешь под передачей объекту C++ "непонятного" для него сообщения?
VD>Наверно нечто вроде: VD>
Почти. Собственно имелось ввиду, взять указатель, привести к void*, а потом откастить к чему-нить не тому. И вот с результатом каста и поработать. Т.е. на низком уровне это будет выглядеть как переходы по несуществующим записям в vtbl и как передача в функции состояний стека и регистров, которые они не ожидают. А если вернуться обратно в текст на C++ и забыть на секунду про vtbl, стек и регистры, то увидим "непонятные" сообщения.
не "ничего не делает", а проверяет соответствие аргументов шаблона некоторым требованиям.
VD>Никакой полезной работы он не производит. А ограничения нужно задавать декларативно, а не эмуляцией бурной деятельности.
А это такой вид декларативности.
А вообще, на основе "эмуляции бурной деятельности" основано очень много всего в метапрограммировании С++ (чего стоят хотя бы пресловутое использование sizeof для принятия решений в compile-time). Извращение, конечно, но задачи решает, и довольно часто успешно прячется от нескромных взглядов в глубине библиотек или helper'ов.
Кстати, эти самые проверки constraint'ов по ссылке можно спрятать еще глубже (я заменил constraint'ы на более подходящие для std::sort):
template< class T >
struct sort_concept_test
: constraint::is_random_access_iterator<T>
, constraint::is_less_than_comparable<
std::iterator_traits<T>::value_type> >
{};
Здравствуйте, pvgoran, Вы писали:
P>По-моему, такой перенос выбора способа позиционирования потока с уровня классов, представляющих поток (File, Socket, Stream, whatever), на уровень отдельных вызовов (функции load_header_and_data) — это совсем даже не просто и не красиво, и вообще выглядит как хак.
Твоя задача абстрагировать частный алгоритм от конкретных реализаций потоков. Вот и выполняй ее, а не полагайся на совподение имен методов.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, pvgoran, Вы писали:
P>А это такой вид декларативности.
Императивный? Шутку понял, смешно. (с)
P>А вообще, на основе "эмуляции бурной деятельности" основано очень много всего в метапрограммировании С++ (чего стоят хотя бы пресловутое использование sizeof для принятия решений в compile-time). Извращение, конечно, но задачи решает, и довольно часто успешно прячется от нескромных взглядов в глубине библиотек или helper'ов.
Метапрограммирование в С++ является примером того "как не надо делать". Это пример полного извращенчиства и маразма. И это при том, что есть языки и фрэймворки в которых метапрограммирвоание выглядит очень достойно.
P>Кстати, эти самые проверки constraint'ов по ссылке можно спрятать еще глубже (я заменил constraint'ы на более подходящие для std::sort):
P>
P>template< class T >
P>struct sort_concept_test
P> : constraint::is_random_access_iterator<T>
P> , constraint::is_less_than_comparable<
P> std::iterator_traits<T>::value_type> >
P> {};
P>
P>Так лучше?
Почти идиально. Осталось понять зачем нужны все эти сложности?
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Метапрограммирование в С++ является примером того "как не надо делать". Это пример полного извращенчиства и маразма. И это при том, что есть языки и фрэймворки в которых метапрограммирвоание выглядит очень достойно.
Так-то оно так, да только метапрограммирование в С++ (при всей его корявости) позволяет добавлять новые качества старым C++ программам. А вот переход на новые языки и фреймворки только для более удобного метапрограмирования с портированием туда же сотен тысяч строк отлаженного и работающего кода -- это не меньшее извращенчество. Другое дело -- объединять C++ код с кодом на других языках. Но и здесь есть свои проблемы.
P>>Кстати, эти самые проверки constraint'ов по ссылке можно спрятать еще глубже (я заменил constraint'ы на более подходящие для std::sort):
P>>
P>>template< class T >
P>>struct sort_concept_test
P>> : constraint::is_random_access_iterator<T>
P>> , constraint::is_less_than_comparable<
P>> std::iterator_traits<T>::value_type> >
P>> {};
P>>
P>>Так лучше?
VD>Почти идиально. Осталось понять зачем нужны все эти сложности?
Чтобы не вводить интерфейс IComparable
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, pvgoran, Вы писали:
P>>По-моему, такой перенос выбора способа позиционирования потока с уровня классов, представляющих поток (File, Socket, Stream, whatever), на уровень отдельных вызовов (функции load_header_and_data) — это совсем даже не просто и не красиво, и вообще выглядит как хак.
VD>Твоя задача абстрагировать частный алгоритм от конкретных реализаций потоков. Вот и выполняй ее, а не полагайся на совподение имен методов.
Я разве предлагал использовать для абстрагирования duck typing?? Я всего лишь указал на концептуальный недостаток (который легко может перерасти в недостаток практический) решения, основанного на делегатах.
Кстати, "полагаться на совпадение имен методов" — это таки решение задачи абстрагирования (если не забывать это самое совпадение как-то поддерживать). Хотя лично мне больше нравится C++-ное решение на основе специализации шаблонов.
Здравствуйте, VladD2, Вы писали:
P>>А это такой вид декларативности.
VD>Императивный? Шутку понял, смешно. (с)
Поясняю. Исполняться этот код не будет (так что императивность — это только форма, но никак не содержание), он интересует нас с точки зрения проверки/декларирования поддержки типом определенных операций.
Вот если бы мы говорили "продвинутому" компилятору "проверь, что можно создавать объекты данного типа, присваивать их и т.п." — это была бы императивность.
VD>Метапрограммирование в С++ является примером того "как не надо делать". Это пример полного извращенчиства и маразма.
В определенных областях это, безусловно, верно. Но кое-что получилось довольно удачно.
VD>И это при том, что есть языки и фрэймворки в которых метапрограммирвоание выглядит очень достойно.
Кстати, какие (кроме Lisp-based)?
P>>Кстати, эти самые проверки constraint'ов по ссылке можно спрятать еще глубже (я заменил constraint'ы на более подходящие для std::sort):
P>>[ skipped ]
P>>Так лучше?
VD>Почти идиально. Осталось понять зачем нужны все эти сложности?
Здравствуйте, pvgoran, Вы писали:
P>Я разве предлагал использовать для абстрагирования duck typing??
А зачем ты тогда влез в этот спор? Ведь даже в сабже ясно о чем идет речь.
P>Я всего лишь указал на концептуальный недостаток (который легко может перерасти в недостаток практический) решения, основанного на делегатах.
Я не вижу никакого концептуального недостатка. Я вижу нежелание принимать непривычных подходов.
Функцональное абстрагирование очень удобная вещь во многих случаях. В отличии от утиной типизации — надежный и бытсрый.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, pvgoran, Вы писали:
P>Поясняю. Исполняться этот код не будет (так что императивность — это только форма, но никак не содержание), он интересует нас с точки зрения проверки/декларирования поддержки типом определенных операций.
Будет не будут. Ты указывашь что делать, а не что нужно. Этот способ просто извращенческий. Это как всегда залатывание дыр языка. Надеюсь, что на основании того, что можно извернуться концепты не будут выброшены из нового стандарта.
P>Вот если бы мы говорили "продвинутому" компилятору "проверь, что можно создавать объекты данного типа, присваивать их и т.п." — это была бы императивность.
Ну, "мы" это уже давно говорим. Правда компилятору C# 2.0. И что характерно совершанно декларативно.
VD>>Метапрограммирование в С++ является примером того "как не надо делать". Это пример полного извращенчиства и маразма.
P>В определенных областях это, безусловно, верно. Но кое-что получилось довольно удачно.
Проблема в том, что решение пригодное только "для кое-чего" не является полноценным. И это при том, что есть языки в которых есть полноценные решения.
Внимание, вопрос! Если видно, что пользователи языка используют побочные эффекты для эмуляции метапрограммирования, то почему бы не ввести в язык полноценную его поддержку? Где драть вроде ясно...
P>Кстати, какие (кроме Lisp-based)?
Препроцессор ОКамла. Он пожалуй подходит больше всего, так как сам ОКамл является компилируемым статически типизированным языком. Так же есть еще куча менее подходящих языков вроде Руби и несколько научных исследований прикручивающих похожие возможности к разным языкам (в том числе Яве и Шарпу).
VD>>Почти идиально. Осталось понять зачем нужны все эти сложности?
P>Какие именно сложности?
Ты видел как выглядят констрэйны в C# 2.0? Они просты и понятны. А то что привел ты — это нагромождение кода.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, pvgoran, Вы писали:
P>>Я разве предлагал использовать для абстрагирования duck typing??
VD>А зачем ты тогда влез в этот спор?
С целью критики неудачного с моей точки зрения решения . Правда, критика получилась неконструктивной — я это попытаюсь сейчас исправить.
Когда я писал то сообщение, "для себя" я имел в виду решение задачи абстрагирования в стиле предложенных Зверьком trait'ов — т.е. реализация требуемой для функции load_header_and_data функциональности пропуска байтов выбирается исходя из класса, представляющего поток.
В предложенном Вами решении — передаче делегата при каждом вызове этой функции — присутствует концептуально лишняя конкретность: реализация функциональности пропуска байтов выбирается заново при каждом вызове функции load_header_and_data.
VD>Ведь даже в сабже ясно о чем идет речь.
Вообще-то, то, что предложили Вы, скорее ближе к ФП, чем к ООП (о котором написано в сабже).
P>>Я всего лишь указал на концептуальный недостаток (который легко может перерасти в недостаток практический) решения, основанного на делегатах.
VD>Я не вижу никакого концептуального недостатка.
Я вроде попробовал пояснить...
VD>Я вижу нежелание принимать непривычных подходов. VD>Функцональное абстрагирование очень удобная вещь во многих случаях.
Вне всякого сомнения.
Только в данном случае этот подход IMHO концептуально неоптимальный. Хотя, возможно, в C# (при отсутствии шаблонов C++) реализация "правильного" (с моей точки зрения) подхода получилась бы слишком громоздкой и потому во многих случаях была бы неоправданной.
Здравствуйте, VladD2, Вы писали:
P>>Поясняю. Исполняться этот код не будет (так что императивность — это только форма, но никак не содержание), он интересует нас с точки зрения проверки/декларирования поддержки типом определенных операций.
VD>Будет не будут. Ты указывашь что делать, а не что нужно.
Не будьте формалистом... Я указываю, что нужна поддержка операций. Указанием "что делать" это не является — по крайней мере, с точки зрения программиста.
VD>Этот способ просто извращенческий. Это как всегда залатывание дыр языка.
Вообще-то, если почистить этот код (заменить "костыли" вроде void (*p)(T) = constraint; на нормальный синтаксис), то получится вполне осмысленная и понятная запись. Хотя и не имеющая вида "T поддерживает концепции X, Y и Z".
(С другой стороны, здесь опять — уже на этапе компиляции — всплывает описанная где-то рядом проблема неформальности "утиных интерфейсов". Хотя IMHO и не так остро. В общем, еще есть над чем подумать.)
P>>Вот если бы мы говорили "продвинутому" компилятору "проверь, что можно создавать объекты данного типа, присваивать их и т.п." — это была бы императивность.
VD>Ну, "мы" это уже давно говорим. Правда компилятору C# 2.0.
Не-а. "Вы" говорите "тип должен удовлетворять констрейнту", а не "проверь констрейнт и выдай ошибку при неудаче" — хотя первое и транслируется во второе компилятором.
VD> И что характерно совершанно декларативно.
Ну да. Но я-то про императивные проверки писал.
VD>>>Метапрограммирование в С++ является примером того "как не надо делать". Это пример полного извращенчиства и маразма.
P>>В определенных областях это, безусловно, верно. Но кое-что получилось довольно удачно.
VD>Проблема в том, что решение пригодное только "для кое-чего" не является полноценным.
Во-первых, я писал не про пригодность, а про удачность. Во-вторых, любое решение пригодно только для ограниченного класса задач.
VD>И это при том, что есть языки в которых есть полноценные решения.
VD>Внимание, вопрос! Если видно, что пользователи языка используют побочные эффекты для эмуляции метапрограммирования, то почему бы не ввести в язык полноценную его поддержку? Где драть вроде ясно...
Это вопрос к комитету... Скорее всего, дело в его децентрализованности (для сложных добавлений нужно принимать сложные решения и согласовывать объемные спецификации), недостатке ресурсов и определенной консервативности ("вообще говоря, C++ не про это").
(Кстати, у меня есть некоторые мысли насчет того, что использование этих "побочных эффектов" для МП не так уж и неправильно, но я пока воздержусь их излагать.)
P>>Кстати, какие (кроме Lisp-based)?
VD>Препроцессор ОКамла. Он пожалуй подходит больше всего, так как сам ОКамл является компилируемым статически типизированным языком.
Хм... Помнится, когда я смотрел ОКамл на этот счет, его МП-возможности мне не понравились. Надо будет глянуть еще раз...
P>>Какие именно сложности?
VD>Ты видел как выглядят констрэйны в C# 2.0?
Не видел.
VD> Они просты и понятны. А то что привел ты — это нагромождение кода.
Т.е. сложность в "нагромождении кода" (которое вроде было квалифицировано словами "почти идеально"?), происходящем от отсутствия в языке специального синтаксиса для констрейнтов?.. Если так — то вопрос "зачем" вообще не стоит — без "сложностей" пока просто нельзя.
Здравствуйте, Voblin, Вы писали:
E>> V>Мне, ортодоксальному поклоннику строгой типизации, эти утиные истории кажутся слишком хлипкой конструкцией.
Наверное, правильнее было бы сказать не строгой, а статической типизации. Т.к. duck typing вполне нормально себя чувствует в динамических языках со строгой (сильной) типизацией.
V> Сначала, может быть, это и работает, но потом могут возникать проблемы. Например: V>"Утиный" код менее очевиден. Не хотел бы я брать на доработку и сопровождение код, в котором Duck typing применён массово. V>Чем больше в системе всяких явно не описанных "соглашений", "умолчаний", "подразумеваний" и т.п., тем меньше у неё шансов добраться до финального релиза. Может быть, я что-то неправильно понял, но вся мощь "утиных историй" проявляется тогда, когда язык программирования не заставляет нас предельно чётко и скрупулёзно описывать понятие "Утка". Один программер что-то там себе подразумевает, а его товарищ может подразумевать что-то чут-чуть другое. Один из них, например, может быть свято уверен, что утки в принципе не умеют летать.
Здесь меня уже давно пытаются в этом убедить. А я, собственно, против этого и не возражаю.
Ну а чего можно возразить? Ну есть unit-тестирование, чтобы постараться выявить моменты нецелевого использования уток на самых ранних стадиях. Ну еще есть такой феномен, как крайне редкое использование уток не по назначению. Наверное потому, что чаще всего утки используются именно там, где известно, что утки -- это именно утки. Есть еще шаблоны C++, которые можно считать чистым duck typing-ом, да еще удачно совмещенным со статической типизацией.
Так же, мне кажется, что оппоненты duck typing-а имеют в виду системы, состоящие из одного процесса, в который было скомпилировано около миллиона строк кода. Причем настолько взаимоувязанного между собой, что переменная объявленная на одном конце приложения обязательно видна в другом конце. Ну что же, может быть, я таких процессов никогда не создавал, не знаю.
Тем не менее, существуют подходы к разработке, когда приложение строится из множество маленьких независимых процессиков, взаимодействующих между собой по IPC. Почему бы внутри каждого из них не использовать duck typing?
V>На мой взгляд, "утиные истории" — это весьма неуклюжая попытка обойти "деревянную" сущность иерархий наследования. И даже когда язык позволяет делать множественное наследование, проблема всё равно окончательно не снимается, а всего лишь загоняется вглубь.
V>Пару лет назад, размышляя над этими проблемами, я дошёл до идеи множественной классификации (см. здесь). Лекарство, конечно, слишком сильнодействующее, но я до сих пор не разуверился в том, что рецепт выписан правильно.
Если довести твою идею до мелких интерфесов, содержащих всего один-два метода, и отказаться от именования интерфейсов, то как раз duck typing и получится. По крайней мере мне так показалось после беглого прочтения твоей статьи. Мне показалось, что изобретатели duck typing-а сделали просто еще один шаг вперед. На который ты не решился.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Наверное, правильнее было бы сказать не строгой, а статической типизации. Т.к. duck typing вполне нормально себя чувствует в динамических языках со строгой (сильной) типизацией.
E>Тем не менее, существуют подходы к разработке, когда приложение строится из множество маленьких независимых процессиков, взаимодействующих между собой по IPC. Почему бы внутри каждого из них не использовать duck typing?
Насколько я понимаю, основная цель применения утиных историй — понижение сложности. И заниматься этим актуально именно там, где приложение очень большое. Для реализации "маленьких независимых процессиков" применение крутых наворотов может оказаться стрельбой из пушки по воробьям.
E>Если довести твою идею до мелких интерфесов, содержащих всего один-два метода, и отказаться от именования интерфейсов, то как раз duck typing и получится. По крайней мере мне так показалось после беглого прочтения твоей статьи. Мне показалось, что изобретатели duck typing-а сделали просто еще один шаг вперед. На который ты не решился.
Вот здесь не согласен. По методике "duck typing" мы создаём классы, которые ведут себя как утка, а по моей измышлятине мы получаем возможность создавать переменные, которые являются уткой помимо того, что являются ещё, например, дичью.
Здравствуйте, Voblin, Вы писали:
E>>Наверное, правильнее было бы сказать не строгой, а статической типизации. Т.к. duck typing вполне нормально себя чувствует в динамических языках со строгой (сильной) типизацией.
V>Ага, особенно когда вот так: V>
Это пример грязного хака в языке со статической, но слабой типизацией. К duck typing-у отношения не имеет.
Попробуй сделать что-нибудь подобное в языке с динамической сильной типизацией. Вот, скажем:
class Duck; def qrack(); end; end
class Dog; def gaff(); end; end
def make_duck(anybody); return anybody; end
duck = make_duck( Dog.new )
duck.qrack()
и получишь то, что следовало ожидать -- исключение:
duck_typing_strong_typing.rb:7: undefined method `qrack' for #<Dog:0x2778060> (NoMethodError)
Вместо Undefined Behaviour в слаботипизированном C++.
E>>Тем не менее, существуют подходы к разработке, когда приложение строится из множество маленьких независимых процессиков, взаимодействующих между собой по IPC. Почему бы внутри каждого из них не использовать duck typing?
V>Насколько я понимаю, основная цель применения утиных историй — понижение сложности.
Не думаю. Основная цель -- уменьшение количества абстракций. Если пользоваться твоими примерами, то для того, чтобы сказать "Здравствуйте", вовсе не нужен интерфейс "Вежливый человек". Достаточно метода "поздоровайся".
V> И заниматься этим актуально именно там, где приложение очень большое. Для реализации "маленьких независимых процессиков" применение крутых наворотов может оказаться стрельбой из пушки по воробьям.
Так в том-то и дело, что для небольших проектов выстраивать концептуально правильную иерархию интерфейсов и реализующих их классов только для того, чтобы внутри generic-а можно было один раз вызвать метод "поздоровайся" -- вот это действительно из пушки по воробьям.
E>>Если довести твою идею до мелких интерфесов, содержащих всего один-два метода, и отказаться от именования интерфейсов, то как раз duck typing и получится. По крайней мере мне так показалось после беглого прочтения твоей статьи. Мне показалось, что изобретатели duck typing-а сделали просто еще один шаг вперед. На который ты не решился.
V>Вот здесь не согласен. По методике "duck typing" мы создаём классы, которые ведут себя как утка, а по моей измышлятине мы получаем возможность создавать переменные, которые являются уткой помимо того, что являются ещё, например, дичью.
Так вся разница в том, что по твоему подходу чтобы быть уткой нужно реализовывать интерфейс "утка". А что есть интерфейс -- набор методов + имя. Если посчитать, что имя интерфейса -- это не нужный рудимент, то остается только набор методов. Если у сущности такой же набор методов, как у утки -- значит это утка и есть. Зачем еще дополнительные ярлыки равешивать?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Это пример грязного хака в языке со статической, но слабой типизацией. К duck typing-у отношения не имеет.
Пардон. Я просто поглумился маленько.
E>Основная цель -- уменьшение количества абстракций. Если пользоваться твоими примерами, то для того, чтобы сказать "Здравствуйте", вовсе не нужен интерфейс "Вежливый человек". Достаточно метода "поздоровайся".
Просто иметь метод — недостаточно. Нужно, чтобы он ещё откуда-то вызывался. В простейшем случае — дать понять "клиенту", что у "сервера" есть такой метод. Подходы могут быть следующими:
1. Прямо сказать клиенту, что ты утка.
2. Иметь возможность ответить на вопрос: "А не утка ли ты?"
3. Иметь возможность ответить на вопрос: "А умеешь ли ты крякать?"
4. Иметь метод SayAnything, который у нас вызовет метод qrack(), а у собаки — gaff().
5. Ухитриться вписываться в контекст таким и только таким образом, что и так всем понятно, что ты утка. Например, вписываться только в коллекцию Утятник или в реквизит УткаПоПекински объекта Ужин.
6....и т.п.
V>>Вот здесь не согласен. По методике "duck typing" мы создаём классы, которые ведут себя как утка, а по моей измышлятине мы получаем возможность создавать переменные, которые являются уткой помимо того, что являются ещё, например, дичью.
E>Так вся разница в том, что по твоему подходу чтобы быть уткой нужно реализовывать интерфейс "утка". А что есть интерфейс -- набор методов + имя. Если посчитать, что имя интерфейса -- это не нужный рудимент, то остается только набор методов. Если у сущности такой же набор методов, как у утки -- значит это утка и есть. Зачем еще дополнительные ярлыки равешивать?
В том то всё и дело, что по моему методу описать и реализовать интерфейс "Утка" нужно всего один раз, а не копиблочить по всем классам, где он нужен.
Справедливый вопрос: а как добавлять специфические фичи к поведению утки, если реализующий его исходный код предлагается (или даже рекомендуется) не трогать? Да очень просто — реализуем эти фичи для сочетаний классов. Например, для сочетания (Утка, Дичь) реакцию на посылку сообщения "УтиУтиУти" можно сделать именно такой, какая характерна для дикой утки.
Здравствуйте, Voblin, Вы писали:
V>Просто иметь метод — недостаточно. Нужно, чтобы он ещё откуда-то вызывался. В простейшем случае — дать понять "клиенту", что у "сервера" есть такой метод.
А зачем? Пусть вызывает то, что считает нужным.
Если мы имеем дело с динамической типизацией, то получим исключение в run-time.
Если со статической (как в C++ шаблонах), то нас остановит компилятор.
Не нужно ничего выдумывать. Если клиент предполагает, что он общается с уткой, то нужно ему сервера-утки и подсовывать. Только и всего.
E>>Так вся разница в том, что по твоему подходу чтобы быть уткой нужно реализовывать интерфейс "утка". А что есть интерфейс -- набор методов + имя. Если посчитать, что имя интерфейса -- это не нужный рудимент, то остается только набор методов. Если у сущности такой же набор методов, как у утки -- значит это утка и есть. Зачем еще дополнительные ярлыки равешивать?
V>В том то всё и дело, что по моему методу описать и реализовать интерфейс "Утка" нужно всего один раз, а не копиблочить по всем классам, где он нужен.
А чем это duck typing-у противоречит?
V>Справедливый вопрос: а как добавлять специфические фичи к поведению утки, если реализующий его исходный код предлагается (или даже рекомендуется) не трогать? Да очень просто — реализуем эти фичи для сочетаний классов. Например, для сочетания (Утка, Дичь) реакцию на посылку сообщения "УтиУтиУти" можно сделать именно такой, какая характерна для дикой утки.
Приведи пример, пожалуйста, a?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуй, VladD2, ты писал (о потоках в .NET):
VD>Думаю, что они это сделали по глупости. Точно так же как использовали базовый класс вместо интерфейса.
Точнее, просто слизали из Delphi, не подумав.. Когда Хейлсберг над Delphi мозговал, интерфейсов там ещё и в помине не было.
Здравствуйте, SilverCloud, Вы писали:
SC>Точнее, просто слизали из Delphi, не подумав.. Когда Хейлсберг над Delphi мозговал, интерфейсов там ещё и в помине не было.
Причем тут дельфи. BCL большей части содрана с Явы. Там те же проблемы. Но когда эти проблемы создавались, то Дельфи даже в проекте не было.
Тут видимо играют роль тонкие дизайнерские изыски. Вот в чем они заключаются я хоть убей понять не могу.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.