Re[3]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 16:09
Оценка: +1 -4 :)
Здравствуйте, eao197, Вы писали:

Ёлы, плалы. Ну, когда же ты наконце научишся приводить примры на 2-4 строчки кода? Зачем ты снова залудил гору кода, ну, не будет в нее никто вникать. Не надейся, что сможешь стать вторым Толстым.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 16:09
Оценка: 24 (1)
Здравствуйте, Кодёнок, Вы писали:

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


Для решения подобных проблем в С++ предлагается добавить нечто вроде констрэйнов из Шарпа. В С++ их предлагается назвать Concepts. Тут недавно дали ссылку на презинтацию Саттера (из МС). Там этот вопрос подробно описан.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Утиные истории vs ООП?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.10.05 16:38
Оценка:
Здравствуйте, Зверёк Харьковский, Вы писали:

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


E>>Есть небольшое отличие: динамические языки проще адаптировать к некоторым условиям. Например, нужно прочитать из потока заголовок, определить, сколько байт нужно пропустить, а затем прочитать еще несколько байт. Если потоком является файл, то можно просто сделать seek, а если потоком является сокет, то пропускаемые данные нужно читать:

E>>
E>>def load_header_and_data( stream )
E>>    h = load_header( stream )
E>>    if stream.respond_to( "seek" )
E>>        stream.seek( h.get_length() )
E>>    else
E>>        stream.read( h.get_length() )
E>>    end
E>>    load_data( stream )
E>>end
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?

ЗХ>либо Б:

ЗХ>
ЗХ>class File
ЗХ>{
ЗХ>...
ЗХ>void skip(int _bytes){seek(_bytes);}
ЗХ>...
ЗХ>}
ЗХ>class Socket
ЗХ>{
ЗХ>...
ЗХ>void skip(int _bytes){read(_bytes);}
ЗХ>...
ЗХ>}
ЗХ>


А вот это означает, что при проектировании классов File и Socket мы должны были предусмотреть возможность операции skip. Но ведь всего не предусмотришь. А попытка предусмотреть приводит к жирным интерфейсам.

ЗХ>А твое решение (как на Руби, так и на С++) совершенно нерасширяемо — завтра у тебя появится еще один тип потока, у которого надо будет вызвать вообще функцию, скажем, move, и что?

ЗХ>Бррр!

А какой другой вариант? Требовать, чтобы все классы наследовались от одного интерфейса (абстрактного базового класса)? Не то ли это развитие событий, против которого возражал McSeem2?
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[4]: Утиные истории vs ООП?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.10.05 16:38
Оценка:
Здравствуйте, 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++.
Re[4]: Утиные истории vs ООП?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.10.05 16:41
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Ёлы, плалы. Ну, когда же ты наконце научишся приводить примры на 2-4 строчки кода? Зачем ты снова залудил гору кода, ну, не будет в нее никто вникать. Не надейся, что сможешь стать вторым Толстым.


Чтобы показать, сколько нужно генерить кода в C++ чтобы получить тот же эффект, что и в двух строчках Ruby или Python-а.



Не нравится, не читай
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[3]: C++ перегрузили
От: GlebZ Россия  
Дата: 07.10.05 16:54
Оценка: +1
Здравствуйте, VladD2, Вы писали:

V>Тут недавно дали ссылку на презинтацию Саттера (из МС).

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

Хотя все остальное очень понравилось. Особенно внедрение OpenMP на уровне языка Active Objects. (пока остальное не досмотрел). Вещь полезная, но боюсь еще больше усложнит программирование.

С уважением, Gleb.
ЗЫ А обсуждение ентого было? Чего-то я не нашел.
Re[5]: Утиные истории vs ООП?
От: Зверёк Харьковский  
Дата: 07.10.05 16:55
Оценка: 9 (1) +1
Здравствуйте, 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 для типа этого потока, не меняя клиентский код.
FAQ — це мiй ай-кью!
Re[5]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 17:01
Оценка:
Здравствуйте, eao197, Вы писали:

E>Здравствуйте, Кодёнок, Вы писали:


E>>>На C++ных шаблонах пришлось бы делать какие-то фокусы со специализацией (плагиат отсюда: Кто хотел определения наличия функции-члена?
Автор: MaximE
Дата: 13.09.03
)

E>>>Чесно говоря, подобный duck typing в C++ меня бы забабахал сразу же.

Кё>>Ну это недостаток лично С++ (дело все лишь в добавлении одной фичи, аналогичной typeof или sizeof), а не статически типизированных языков.


E>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами? Можно ли там будет добавить такое ключевое слово, которое можно будет применить к параметру шаблона?

E>
E>void LoadHeaderAndData( T stream )
E>{
E>    Header h = LoadHeader( stream );
E>    if( stream.HasMethod( "Seek" ) )
E>        stream.Seek( h.GetLength() );
E>    else
E>        stream.Load( h.GetLength() );
E>    LoadData( stream );
E>}
E>


E>или же там все это нужно через рефлекшен делать (только толку тогда от такой статической типизации)?


Ты не поверишь! В C# для описания интерфейсов классов используются... интерфейсы!
В дженериках введена возможность описывать требования к параметрам типов с помощью ключевого слова where. Выгядит это так:
interface IWorker
{
    void DoWork();
}

class A
{
    // Статически описанное ограничение. Код не скомпилируется если тип
    // используемый как аргумент типа данной функции не будет реализовывать
    // worker
    public 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
Автор: VladD2
Дата: 07.10.05
.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 17:01
Оценка:
Здравствуйте, eao197, Вы писали:

E>Да в том-то и дело, если почитать Re[15]: Следующий язык программирования
Автор: VladD2
Дата: 07.10.05
, то оказывается, что в отличии от generic-ов Java, generic-и C# предназначены для инстанциирования в run-time (не compile-time). Поэтому компилятор на этапе компиляции приведенного тобой кода не сможет понять, какая сигнатура будет у метода Seek.


Отличия с Явой тут нет. Явские дженерики тоже рантаймная сущьность. Просто они обрабатываются за счет полиморфизма.

А описание ограничений на параметры типа задается по разному. В Шарпе для этого служат констрэйны, а в Яве маски.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 17:01
Оценка:
Здравствуйте, Sinclair, Вы писали:

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

E>>Ok. А как бы это выглядело со статически типизированным C# и его generic-ами?
S>С дженериками — никак. Потому, что такая базовая вещь, как поток, не должна вводиться через дженерик. Вот так выглядит код на реальном шарпе:

S>
S>public void LoadHeaderAndData(Stream stream)
S>{
S>  Header h = LoadHeader(stream);
S>  if(stream.CanSeek())
S>    stream.Seek(h.GetSkipLength());
S>  else
S>  {
S>    byte[] buffer = new byte[h.GetSkipLength()];
S>    stream.Read(buffer, 0, h.GetSkipLength());
S>  }
S>  LoadData(stream);
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>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 17:01
Оценка: +1
Здравствуйте, Dyoma, Вы писали:

D>Более того, типизация несколько мешает ОО. Чистое объектное видение мира подразумевает, что любой объект потенциально может отреагировать на любое сообщение. Или другими словами есть только объекты и объекты не различаются ничем, кроме своего поведения. Например, к каждому человеку можно подойти с вопросом "Сколько ты будешь делать?" с параметром ТЗ. На что можно получить в ответ "N дней", "Чего???" или по зубам . Но коллапса с миром не случит в любом случае.


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

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

D>"Утиные истории" появились задолго до ОО, например тот же Lisp.


+1
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 17:01
Оценка:
Здравствуйте, 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>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Утиные истории vs ООП?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 17:14
Оценка: :)
Здравствуйте, eao197, Вы писали:

VD>>Ёлы, плалы. Ну, когда же ты наконце научишся приводить примры на 2-4 строчки кода? Зачем ты снова залудил гору кода, ну, не будет в нее никто вникать. Не надейся, что сможешь стать вторым Толстым.


E>Чтобы показать, сколько нужно генерить кода в C++ чтобы получить тот же эффект, что и в двух строчках Ruby или Python-а.


Ты оставь такие показы тем кто про бусты рассказывает. Глядишь они тоже в пару строк уложатся.
Пойми, я уже не в первый раз вижу у тебя примеры которые даже читать не хочется. А потом ты спрашиваешь почему на них никто не реагирует. Вот именно по тому. Нужно учиться формулировать задачи так, чтобы их можно было понять не напрягая все иуственные усилия. Ведь сами задачи то не сложные. Ты лучше чуть больше на естественном языке напиши.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: C++ перегрузили
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.10.05 17:14
Оценка:
Здравствуйте, GlebZ, Вы писали:

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


V>>Тут недавно дали ссылку на презинтацию Саттера (из МС).

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

Не, эти расширения выпучены. Большое заблцждение, что С++ имеет сложный синтаксис. С++ имеет сложную семантику. И такие усложения синтаксиса позволят значительно упростить семантику.

Понимаеш ли... на практике сложность С++ для программиста проистекает не из того, что код прочесть очень сложно. А из того, что порой мельчайшее непонимани или ошибка приводит к тупиковой ситуации из которой только прожженный в боях гуру сможет выйти с честью. Вот тут
Автор: VladD2
Дата: 07.10.05
пример возникновения подобной стуации на ровном месте.

Так вот новые раширения как раз призваны устранить подобные рпоблемы.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Утиные истории vs ООП?
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.10.05 17:23
Оценка:
Здравствуйте, 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>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: Утиные истории vs ООП?
От: Зверёк Харьковский  
Дата: 07.10.05 17:36
Оценка: +1
Здравствуйте, 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 лет использоания
FAQ — це мiй ай-кью!
Re[2]: Утиные истории vs ООП?
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.10.05 18:23
Оценка:
Здравствуйте, 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>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Утиные истории vs ООП?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.10.05 18:27
Оценка:
Мне тут подумалось, что 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 ); }


либо в C# (могу ошибится в синтаксисе):
void get_voice( T beast )
    where T.HasMethod( void Crack() ) || T.HasMethod( void Gaff() )
{
    if( T.HasMethod( void Crack() ) )
        beast.Crack();
    else
        beast.Gaff();
}
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[6]: Утиные истории vs ООП?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.10.05 18:49
Оценка:
Здравствуйте, Зверёк Харьковский, Вы писали:

E>>Код load_header_and_data стал понятнее. А хак, хоть и спрятанный, остался.


ЗХ>Я предвидел такой ответ




ЗХ>Тем не менее, я, скажем, могу представить случай, в котором функция seek у объекта будет, но, скажем, заведомо неэффективная (или и вовсе — с тем же именем, но с другим смыслом).


ЗХ>здесь возникает философский вопрос: а кто должен знать нужный способ? Мне (пока) наиболее изящным и, скажем так, прямым решением кажется концепция traits:

ЗХ>
ЗХ>traits< stream >.move_forward(stream, bytes);
ЗХ>


ЗХ>Здесь и интерфейс 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++.
Re[6]: Утиные истории vs ООП?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.10.05 18:50
Оценка:
Здравствуйте, Зверёк Харьковский, Вы писали:

E>>А тебе не кажется, что эта специализация, фактически, эквивалентна приведенному мной выше skip_data?


ЗХ>Казалось бы. Ан нет.

ЗХ>Твой вариант — при появлении нового типа потока, требует модификации функции skip_data (которая может быть и недоступна).

Можно через переименование: Re[6]: Утиные истории vs ООП?
Автор: eao197
Дата: 07.10.05

Причем это можно делать даже без останова приложения.

ЗХ>Мой вариант — при появлении нового типа потока создаем еще одну (отдельную и независимую специализацию).


Но доступную только во время компиляции.

ЗХ>Второе отличие — очередное проявление "статика vs. динамика". В моем варианте, если неизвестно, как обрабатывать новый тип потока (нет специализации seek) — мне моментально ругнется компилятор. В твоем — если функция skip_data не учла новый тип потока — ругнется рантайм "когда-нибудь" — может быть, через 5 лет использоания


Имхо, duck typing возможен и для статически типизированных языков: Re: Утиные истории vs ООП?
Автор: eao197
Дата: 07.10.05
.
... << RSDN@Home 1.1.4 stable rev. 510>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.