Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>BTW, идеальной "записью намерений" для этого случая мне кажется что-то в этом роде: ЗХ>
ЗХ>int sum = 0;
ЗХ>sum += each(array); //ну или each(array.begin, array.end);
ЗХ>
ЗХ>Читается "прибавить к сумме каждый элемент последовательности". Поддается легкой модификации: ЗХ>а) что сделать с элементами (например, += заменим на *=) ЗХ>б) с какими элементами (заменяем each на select(последовательность, условие) )
Очень интересная идея. Ты ее сам придумал или взял откуда? Если взял, то поделись где, хочу по-больше узнать.
А пока по-больше не узнал, то есть у меня такой вопрос о расширяемости.
+= и *= — это понятно. Более того понятно даже как можно расширить, если я, к примеру, захочу прямоугольники пересекать:
Rectangle intersection = Rectangle.PLANE; // Забудем о пересечении пустого набора, не о ней речь...
intersection = each(array).intersect(intersection); // Для каждого элемента вызвать intersect с параметром, текущее значение intersection
Все хорошо, до тех пор, пока у элементов коллекции есть нужный мне метод. Но что делать если я хочу применить очень специфическую аккумуляцию? Например, я хочу сложить только четные элементы массива. Можно поступить так:
int evenSum = 0;
evenSum += eventOrZero(each(array));
int evenOrZero(int x) {
return x % 2 == 0 ? x : 0;
}
Но тут мы потеряли исходную красоту, и получили почти C++ (забыл как этот алггоритм в stl называется, пусть for_each будет):
int addEven(int sum, int x) {
return sum + (x % 2 == 0 ? x : 0);
}
int evenSum = for_each(array.begin, array.end, addEven);
Ну или можно типа C# лямбду применить
int evenSum = ((sum, x) => sum + (x % 2 == 0 ? x : 0)) (0, each(array));
Но уж больно исходный inject:into: напоминает...
А если пойдем еще дальше и потребуется вызывать размые методы (применять разные операции) в зависимости от значения очередного элемента? Например, четные прибывляем, на нечетные умножаем. Тут вроде как надо явно указать промежуточный результат.
Здравствуйте, VladD2, Вы писали:
VD>А на месте ПК я бы постыдился бы ставить плюсы таким заблуждениям. VD>Почему только ПК? Ну, eao197 только что сам "плавл" в этом вопросе. Я вообще даже представить не мог, что на нашем сайте в таких вопросах народ "плавает".
Опустил! Опустил ниже плинтуса!
Ладно, предлагаю конструктивно поговорить на тему, что мы привыкли называть полиморфизмом. Вот, например такой код:
if (area.containsPoint(mouseLocation) {
... // Что-нить сделаем
}
Это есть пример использования полиморфизма, а именно использование метода containsPoint(Point). Но, в языке со статической типизацией, что бы данный алгоритм был полиморфным, нужно еще определить интерфейс, например так:
class Area {
virtual bool containsPoint(Point p) = 0;
}
Получается, что здесь мы тоже используем полиморфизм.
А еще надо сделать реализацию:
class Rectangle : public Area {
virtual bool containsPoint(Point& p) {
return (x >= p.x) && ...;
}
}
Написано, что Rectangle наследуется от Area для того, что бы его можно было использовать как Area, в частности в исходном if. А значит и здесь мы используем полиморфизм.
Это был полиморфизм динамический переходим к статическому. Перегрузку пропустим — imho не интересно. Возьмемся за шаблоны:
template<class T>
T sum(iterator<T> begin, iterator<T> end) {
T result = T();
while (begin != end) {
result = result + *begin;
begin++;
}
return result;
}
class Vector2D {
Vector2D operator+(Vector2D& v) { return Vector2D(x+v.x, y+v.y); }
}
Опять видим два использования полиморфизма. Один — алгоритм вычисления суммы, элементов, умеющих складываться. Второй — определение сложения для векторов, для общего удобства и применения алгоритма sum к векторам. В данном случае интерфейс из оператора + за нас определен в стандарте C++.
Я предлагаю взгянуть на этот так: Здесь описан интерфейс
int factorial<int>();
И две его имплементации. Для 0 и для всех остальных целых.
А можно было определить и так:
int factorial<0>() { return 1; }
int factorial<1>() { return 1; }
int factorial<2>() { return 2; }
....
int factorial<12>() { return 479001600; } // Вроде это последний какой в 32 бита влезет
template<int N>
int factorial() {
error "int overflow or negative";
}
А теперь сам полиморфный алгоритм:
int f = factorial<10>();
А теперь вопрос, что тебя особо смущает в примере с factorial?
Здравствуйте, eao197, Вы писали:
E>Продолжим разговор про синхронность и асинхронность.
E>В том то и дело, что традиция эта в таких языках, как C++, Java, C# и пр. зашита намертво. Если нам в 5% случаев потребуется именно fire and forget, то сделать это в том же самом синтаксисе, что и обычный call, мы уже не сможем. А вот если бы смогли, это бы я и считал прорывом.
Ну, давай изобретем синтаксис. Например...
О!
p.Call(); // синхронный вызов p->Call(); // fire and forget;
E>А теперь расскажи, если не сложно, как ты видишь здесь детерминизм в виде синхронных вызовов, если объекты B, C и D должны работать на разных нитях. Во что все это выльется?
В работу в одной нити, конечно же. Потому что нет возможности добиться детерминированной работы в честно одновременной реализации. Все равно все, получается, будут вынуждены ждать друг друга.
Надо либо изобретать другую схему, либо возвращаться к однопоточной работе.
Либо требовать от классов готовности к неупорядоченному приходу сообщений.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Cyberax, Вы писали:
>> C>А вообще, идея с бинарным репозиторием исходных файлов, с которым >> IDE работает напрямую, была опробована в VisualAge for Java. Успешно >> провалилась >> Так дело не бинарности/текстовости. А в том, что единицей >> версионирования является файл, а не функция (или еще какая часть AST).
C>Так VAJе ведь именно так и было — пользователь работал на уровне C>элементов AST.
Так какие были проблемы, кроме нестабильности самого VAJ?
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, Sinclair, Вы писали:
E>Совпадения не случайные (по-моему про случайность я и не говорил). Есть два типа PDU -- deliver_sm и data_sm. Они могут применятся для одной и той же задачи: доставки входящего SMS. Т.к. задача одинаковая, то и многие параметры у этих PDU одинаковые. Но deliver_sm не есть data_sm, т.к., в отличии от data_sm, deliver_sm применяется только для входящих SMS. А data_sm может применятся и для исходящих. В свою очередь, data_sm, не есть deliver_sm, т.к. у deliver_sm больше параметров.
Это все прекрасно и понятно. E>Вот упрощенные спецификации:
E>Пересечения методов не случайно. Однако, я не думаю, что имеет смысл объединять общие методы классов deliver_sm_t и data_sm_t в какой-то интерфейс. E>Хотя бы потому, что часть этих методов присутствуют и в других типах PDU.
Великолепно! Я бы наборот, увидел в этом перст судьбы: двукратное повторение — еще не повод. Но если оказывается, что некоторый набор методов встречаетя многократно —
это верный знак наличия стабильного интерфейса. Который лучше всего сразу же и задекларировать. Благо стоит это недорого, даже без средств автоматизации рефакторинга. E> Например, broadcast_sm (source_addr_ton, source_addr_npi, source_addr, sm_default_msg_id), cancel_sm (source_addr*, dest_addr*). E>Если идти по такому пути, то придется плодить массу мелких интерфейсвов (pdu_with_source_addr_t, pdu_with_dest_addr_t, pdu_with_data_coding_t, ...).
Именно. Имена только неудачные. E> Но зачем?
Как зачем? В первую очередь для документирования этой общности. Вот, например, посмотрел ты на массив, и увидел у него метод GetEnumerator. Посмотрел на список, и увидел у него метод GetEnumerator. Подумал — и зафиксировал интерфейс IEnumerable. Который специфицирует некоторое общее поведение. E>Способы обработки PDU могут быть разными. Где-то, например, только поле service_type контролируется, где-то только data_coding.
Можно плясать от обратного — не от сравнения интерфейсов классов, а от использования. В каком-то контексте есть согласованное использование подмножества методов — бах, выделил их в интерфейс.
E>Мы наверное говорим о разных use-case. Я имел ввиду, что в одном модуле приложения потребовалось как-то обрабатывать и deliver_sm, и data_sm. А во втором модуле -- как deliver_sm, data_sm, так и submit_multi_sm. Две разных, независимых задачи. В разных модулях.
Ну, всякое конечно бывает. Не повезло значит — та задача требовала одновременно несколько интерфейсов, а мы по ошибке запихали все в один интерфейс. Вот, например. в дотнете у IEnumerable свойства Count нету. А у IList — есть.
Ничего страшного — это же наш интерфейс. Отрефакторим его пополам. Все равно рано или поздно набор примитивов стабилизируется. А после небольшого опыта такого подхода (пять-шесть лет) ты будешь видеть эти интерфейсы сразу же, еще до того, как начнешь писать классы.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Andrei N.Sobchuck wrote:
> C>Так VAJе ведь именно так и было — пользователь работал на уровне > C>элементов AST. > Так какие были проблемы, кроме нестабильности самого VAJ?
Ну просто неудобно это было. Может для какого-нибудь Smalltalk'а среды
типа VAJ — это и нормально, но для Java они не очень-то и подходят.
Проще работать с программой как с _текстом_, а не с синтаксическим
деревом представленым визуально.
Здравствуйте, Dyoma, Вы писали:
ЗХ>>BTW, идеальной "записью намерений" для этого случая мне кажется что-то в этом роде: ЗХ>>
ЗХ>>int sum = 0;
ЗХ>>sum += each(array); //ну или each(array.begin, array.end);
ЗХ>>
ЗХ>>Читается "прибавить к сумме каждый элемент последовательности". Поддается легкой модификации: ЗХ>>а) что сделать с элементами (например, += заменим на *=) ЗХ>>б) с какими элементами (заменяем each на select(последовательность, условие) )
D>Очень интересная идея. Ты ее сам придумал или взял откуда?
С одной стороны, вроде бы сам — пока сообщение писал.
С другой стороны, сейчас я вспомнил — было обсуждение, что такое вроде есть в последнем Perl'e, и как бы это ввести в С++. Где-то здесь, в форуме С++ было.
D>Если взял, то поделись где, хочу по-больше узнать. D>А пока по-больше не узнал, то есть у меня такой вопрос о расширяемости. D>+= и *= — это понятно. Более того понятно даже как можно расширить, если я, к примеру, захочу прямоугольники пересекать: D>
D>Rectangle intersection = Rectangle.PLANE; // Забудем о пересечении пустого набора, не о ней речь...
D>intersection = each(array).intersect(intersection); // Для каждого элемента вызвать intersect с параметром, текущее значение intersection
D>
К слову сказать, бльшАя часть этих форичей, имхо, обрабатывает вот такие простые случаи — которые можно было бы сильно упростить таким each-ем.
D>Все хорошо, до тех пор, пока у элементов коллекции есть нужный мне метод. Но что делать если я хочу применить очень специфическую аккумуляцию? Например, я хочу сложить только четные элементы массива. Можно поступить так: D>
Вроде лакониччненько
D>Но уж больно исходный inject:into: напоминает... D>А если пойдем еще дальше и потребуется вызывать размые методы (применять разные операции) в зависимости от значения очередного элемента? Например, четные прибывляем, на нечетные умножаем. Тут вроде как надо явно указать промежуточный результат.
Здравствуйте, eao197, Вы писали:
E>Просто, исходя из смысла слов "отсылать" (send) и "вызывать" (call) не следует считать, что "отсылка" == "вызову". А в большинстве языков так и есть, причем изменить ничего нельзя.
Перечисли, пожалуйста, языки используещие ассинхронные механимы передачи сообщений.
Даю подсказку, Смолток, откуда ты выдрал эту терминалогию посылает сообщения синхронно.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Многие существенные стороны С++ в C# также остались за бортом (value-семантика, детерминированное разрушение, латентная типизация и т.п.), так что, действительно, можно о происхождении C# от C++ речи не вести
> Есть дженерики, которые и по идеологии, и по реализации сильно отличаются от плюсов. А почему их добавили ясно. Поняли, что языкам нехватает статического полимпорфизма.
Проще говоря, устали в коллекциях приводить типы из Object обратно в тип, который в колеекцию положили. Собственно, возможности generics в C# и Java примерно этим (пока?) по большей части и ограничиваются, если не считать оптимизаций, доступных в generics C# для value-типов.
> А это тут не причем. Хотя тех кто их использут достаточно. Важен дух программированя. В С++ очень часто из-за боязни тормозов вызываемых проверками типов и динамическим полипорфизмом отказываются от чистого ОО-дизайна и испльзуют разные не-ОО-приемы.
В C++ "отказываются от чистого ОО-дизайна" не из-за "боязни тормозов", а из-за понимания, что "чистое ООП" имеет достаточно ограниченную применимость, и для ряда моментов разные другие приемы приводят к более удачному (гибкому, понятному и т.п.) дизайну.
> На дизайн приложения при этом уже начхать. Мы же 4 байта сэкономили!
Дело не столько в экономии, сколько в существенном изменении семантики класса при добавлении к нему виртуальной функции. Например, его уже нельзя побайтно записать/прочитать из файла.
> функция без параметров: >
> void f();
>
> рассматривается как: >
> void f(...);
>
> в С <...>
Уточнение: она не так в C рассматривается. Она рассматривается как функция, информация о параметрах которой отсутствует. А объявление:
void f(...);
в C вообще допустимым не является.
> Разница между дженериками и шаблонами заключается в том, что в дженериках те самые контракты можно задать явно при создании дженериков. А в С++ они определяются неявно в момент вопложения шаблона. При этом копилятор С++ просто пораждает код по шаблону, а потом пытается проверить рпзультат на корректность.
Разница не столько в явном или неявном задании контрактов, сколько в том, какого рода контракты предполагаются или явно задаются там и там. В C# это (по большей части) требование реализовать тот или иной интерфейс. В C++ -- требование к типу обладать некоторой структурой. Concepts это только позволят задавать явно. Наследования от каких-либо интерфейсов для этого нужно не будет. Кроме явного задания concepts ничего от текущей ситуации в C++ не изменится.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Нда... тяжело было почитать приведенные ссылки?
D>Ладно, предлагаю конструктивно поговорить на тему, что мы привыкли называть полиморфизмом. Вот, например такой код: D>
D>Это есть пример использования полиморфизма, а именно использование метода containsPoint(Point).
Откуда видно, что он полиморфен?
D>
D>class Area {
D> virtual bool containsPoint(Point p) = 0;
D>class Rectangle : public Area {
D> virtual bool containsPoint(Point& p) {
D>
Вот теперь это можно утверждать. У нас есть два типа к которым может применяться один метод.
Формализуем сигнатуру этого метода, чтобы далее было проще рассуждать.
Area * Point -> bool
Rectangle * Point -> bool
Если не ясна запись, поясню... Мы имеем метод который можно представить как пару методов:
1. Принимает в качестве параметра аргументы типа Area, Point и возвращающий значение типа bool.
2. Принимает метод принимает аргументы типа Rectangle, Point и возвращает bool.
Собственно данное описание подразумевает, чтом может существовать неограниченное множетсво методов с сигнатурой:
T * Point -> bool
Где T тип являющийся наследником типа Area.
Второй метод как раз в него входит. D>Написано, что Rectangle наследуется от Area для того, что бы его можно было использовать как Area, в частности в исходном if. А значит и здесь мы используем полиморфизм.
D>Это был полиморфизм динамический переходим к статическому. Перегрузку пропустим — imho не интересно.
Обрати внимание на выделенный фрагмент. Ниже мы к нему вернемся.
D> Возьмемся за шаблоны: D>
D>template<class T>
D>T sum(iterator<T> begin, iterator<T> end) {
D> T result = T();
D> while (begin != end) {
D> result = result + *begin;
D> begin++;
D> }
D> return result;
D>}
D>
Да, это параметрический полиморфизм. В данном случае имеем метод с сигнатурой:
iterator<T> * iterator<T> -> T
Где T — это множество типов для которых применима операция сложения.
D>Опять видим два использования полиморфизма. Один — алгоритм вычисления суммы, элементов, умеющих складываться. Второй — определение сложения для векторов, для общего удобства и применения алгоритма sum к векторам. В данном случае интерфейс из оператора + за нас определен в стандарте C++.
Ага. Это пример перегрузки методов про которое ты сказал, что она не интересна.
Полморфен в данном случае сам опрератор "+". Но он и до этого был в С++ плиморфен, так как его можно было применять к типам int, char, double... Ты всего лишь добавил поддержку для еще одного типа, т.е. сделал еще дну перегрузку.
D>Теперь вернемся к факториалу: D>>>
D>Я предлагаю взгянуть на этот так: Здесь описан интерфейс D>
D>int factorial<int>();
D>
D>И две его имплементации. Для 0 и для всех остальных целых.
D>А можно было определить и так: D>
D>int factorial<0>() { return 1; }
D>int factorial<1>() { return 1; }
D>int factorial<2>() { return 2; }
D>....
D>int factorial<12>() { return 479001600; } // Вроде это последний какой в 32 бита влезет
D>template<int N>
D>int factorial() {
D> error "int overflow or negative";
D>}
D>
Замечательно. Теперь опишим сигнатуру этой функции:
void -> int
Все! Больше сигнатур породить нельзя! Параметр шаблона в данном случае это константа! Значение этой константы у всех реализаций метода имеет динаковый тип. Причем этот тип вообще нельзя ипользовать для задания типов внутри метода!
Таким образом мы получаем набор неполиморфных функций. В заблуждение же вводит схожесть синтаксисам с полиморфными описаниями. Но если присмотреться, то можно заметить, что в отличии от этой функции у действительно полиморфных сигнатура будет отличаться. Так для метода фанк:
Множество допустимых типов будет определяться исключительно содержимым полиморфного метода (так как код накладывает неявные ограничения на это множество). Однако то что С++ допускает явную специализацию позволяет утверждать, что этот список потенциально бесконечен.
D>А теперь сам полиморфный алгоритм: D>
D>int f = factorial<10>();
D>
Продолжим список:
int f1 = factorial<1>();
int f2 = factorial<2>();
int f3 = factorial<3>();
int f4 = factorial<4>();
Не находишь, что разница тут только в названии метода? Если придерживаться твоей логики, то полиморфными будут и такие вызовы:
int f1 = factorial1();
int f2 = factorial2();
int f3 = factorial3();
int f4 = factorial4();
А собственно в чем между ними разница? Неужели то что первые сгенерированы компилятором в следствии выполнения метапрограммы, а вторные созданы вручную может влиять на свойства метода вроде полиморфизма? А если я, как ты предлагал, специализирую все варианты явно? В чем разница между:
template<int i> int func();
template<> int func<1>() { return 1; }
template<> int func<2>() { return 2; }
template<> int func<3>() { return 3; }
template<> int func<4>() { return 4; }
template<> int func<5>() { return 5; }
и:
int func1() { return 1; }
int func2() { return 2; }
int func3() { return 3; }
int func4() { return 4; }
int func5() { return 5; }
D>А теперь вопрос, что тебя особо смущает в примере с factorial?
Меня смущает, то что ты неверно трактуешь понятие полиморфизма. Причем делаешь это даже после того как я дал ссылку на довольно внятных источник описывающий значение этого термина. Ну, и еще смущает, неадекватность тех кто поставил тебе оценку, так как один из них является лучшим С++-ника рунета (думаю, это небудет приувеличением).
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Многие существенные стороны С++ в C# также остались за бортом (value-семантика, детерминированное разрушение, латентная типизация и т.п.), так что, действительно, можно о происхождении C# от C++ речи не вести
+1
ПК>Проще говоря, устали в коллекциях приводить типы из Object обратно в тип, который в колеекцию положили. Собственно, возможности generics в C# и Java примерно этим (пока?) по большей части и ограничиваются, если не считать оптимизаций, доступных в generics C# для value-типов.
Коллекции — это то где отсуствие средств обобщенного программирования проявляется особенно ярко. Вспомним, ради чегов С++ были введены шаблоны. Первым посылом был именно создать библиотеку для работы с коллекциями (контейнерами в С++-терминологии).
Однако дженерики пригодны везде где требуется статический полиморфизм. А то что ты все время пыташся принизить их значимость и выдать их за эдакую несостоятельную реализацию — это эже твои личные психологические проблемы.
ПК>В C++ "отказываются от чистого ОО-дизайна" не из-за "боязни тормозов", а из-за понимания, что "чистое ООП" имеет достаточно ограниченную применимость, и для ряда моментов разные другие приемы приводят к более удачному (гибкому, понятному и т.п.) дизайну.
Да, да, да. Как я незаметил? Конечно! Делается это именно из благих побудждений сделать куда более лучший дизайн. Что там еще С++ прилично поддерживает? Процедурное программирование? Ну, конечно! Ведь с его помощью почти все задачи решаются куда лучше чем с помощью ООП. А вот это — этак недоразумение.
Я бы поверил в эту сказку, если бы не видел примеры такого "лучшего" дизайна. И если бы сам не искушался бы соблазном нахимичить ради скорости.
>> На дизайн приложения при этом уже начхать. Мы же 4 байта сэкономили!
ПК>Дело не столько в экономии, сколько в существенном изменении семантики класса при добавлении к нему виртуальной функции. Например, его уже нельзя побайтно записать/прочитать из файла.
Ага. Согласен. Я от желания побайтно скопировать объекты отвыкал где-то год. Страшно приставучее заболевание. И конечно же такое копирование делается не из-за экономии времени, а ради лучшего дизайна. Ну, для улучшения качества и количества GPF-ов.
ПК>Уточнение: она не так в C рассматривается. Она рассматривается как функция, информация о параметрах которой отсутствует.
Это и есть с переменным количеством параметров. В С из-за этого параметры раком в стэк запихивают.
ПК> А объявление: ПК>
ПК>void f(...);
ПК>
ПК>в C вообще допустимым не является.
Ну, да. Главное что интерпретация "void f();" разная. Если ты еще не забыл, я как бы говорил, о том что С и С++ совместимы не на 100 процентов. Корректный С-файл не обязан так же быть корректным С++-фйлом. А стало быть и проблем 100-ой совместимости нет.
>> Разница между дженериками и шаблонами заключается в том, что в дженериках те самые контракты можно задать явно при создании дженериков. А в С++ они определяются неявно в момент вопложения шаблона. При этом копилятор С++ просто пораждает код по шаблону, а потом пытается проверить рпзультат на корректность.
ПК>Разница не столько в явном или неявном задании контрактов, сколько в том, какого рода контракты предполагаются или явно задаются там и там.
Нет, уж, позволь... Как раз это основное отличие. Оно и приводит к неприминимости шаблонов в дотете. Ведь шаблоны можно будет использовать только внутри одного модуля. Библиотеки же останутся кривыми или через некоторое время весь их код окажется в шаблонах. Собственно это мы и наблюдаем в С++ сегодня.
ПК> В C# это (по большей части) требование реализовать тот или иной интерфейс.
И это тоже. Но это уже отличие не системное.
ПК> В C++ -- требование к типу обладать некоторой структурой. Concepts это только позволят задавать явно. Наследования от каких-либо интерфейсов для этого нужно не будет.
Ну, да. Те самые "утиные интерфейсы". В принципе удачное решение некоторых пробем С++.
ПК>Кроме явного задания concepts ничего от текущей ситуации в C++ не изменится.
Хм. Надеюсь изменится. Все же та задница которая существует с шаблонами сейчас, когда одна опечатка может привести к натуральному словестному поносу компилятора из которого ровным счетом ничего нельзя понять, надеюсь исчезнет. Хотя concept будет ведь необязательным... Ну, да хоть разные АТЛ-и с СТЛ-ями перепишут.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Sinclair, Вы писали:
E>>В том то и дело, что традиция эта в таких языках, как C++, Java, C# и пр. зашита намертво. Если нам в 5% случаев потребуется именно fire and forget, то сделать это в том же самом синтаксисе, что и обычный call, мы уже не сможем. А вот если бы смогли, это бы я и считал прорывом. S>Ну, давай изобретем синтаксис. Например... S>О! S>p.Call(); // синхронный вызов p->>Call(); // fire and forget;
Нет, мне больше нравится другой подход: не каждый метод приспособлен для того, чтобы исполнятся асинхронно. Хотя бы из-за необходимости синхронизации доступа к атрибутам объекта. Поэтому лучше объявлять метод асинхронным в описании класса, а для вызова использовать ту же самую нотацию:
class Big_Calculator
{
public :
async some_calculation();
};
Big_Calculator c;
c.some_calculation(); // fire and forget.
E>>А теперь расскажи, если не сложно, как ты видишь здесь детерминизм в виде синхронных вызовов, если объекты B, C и D должны работать на разных нитях. Во что все это выльется? S>В работу в одной нити, конечно же. Потому что нет возможности добиться детерминированной работы в честно одновременной реализации. Все равно все, получается, будут вынуждены ждать друг друга.
Вовсе не обязательно.
S>Надо либо изобретать другую схему, либо возвращаться к однопоточной работе.
Вот, например, такая схема. У нас есть объекты A, B, C и D. Каждый работает на свой нити (активные объекты). Объект A отсылает сообщение a объектам B и C. После завершения обработки этого сообщения объект B отсылает сообщение b, а объект C -- сообщение c. Эти сообщения должны поступить объекту D, причем сначала b, затем c. Сложность в том, что B может заканчивать свою обработку после объекта C.
Для начала будем считать, что цепочка операций A (a) -> B, C; B(b) -> D, C(c) -> D является транзакцией. Если затем объект A породит следуюшее сообщение a (a2), то эта же цепочка повторится в виде новой транзакции: A(a2) -> B, C; B(b2) -> D, C(c2) -> D.
Для поддержки такой синхронизации сообщений bi, ci нам нужен некий монитор транзакции Mi (где i -- номер транзакции). Пусть этот монитор будет представлять из себя следующий конечный автомат:
начальное состояние "wait_b_c";
в состоянии "wait_b_c":
если поступает сообщение b:
сохраняем сообщение b;
переходим в состояние "wait_c";
если поступает сообщение c:
сохраняем сообщение c;
переходим в состояние "wait_b";
в состоянии "wait_c":
если поступает сообщение с:
отсылаем объекту D сообщения b, c;
завершаем работу;
в состоянии "wait_b":
если поступает сообщение b:
отсылаем объекту D сообщения b, c;
завершаем работу;
При наличии такого конечного автомата работа объектов A, B, C выглядит следующим образом: Объект A
создаем монитор Mi;
отсылаем сообщение ai объектам B, C;
Объект B
ожидаем сообщения ai;
обрабатываем сообщение ai;
отсылаем сообщение bi (но так, чтобы его получил монитор Mi);
Объект C
ожидаем сообщения ai;
обрабатываем сообщение ai;
отсылаем сообщение ci (но так, чтобы его получил монитор Mi);
Собственно все. Объекты A, B, C, D работают на своих контекстах. Мониторам Mi собственный контекст не нужен, т.к. они вступают в дело только в редкие моменты времени (для их активации можно либо использовать контекст либого из активных объектов, либо отдельный контекст для не активных /пассивных/ объектов).
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Sinclair, Вы писали:
E>>Если идти по такому пути, то придется плодить массу мелких интерфейсвов (pdu_with_source_addr_t, pdu_with_dest_addr_t, pdu_with_data_coding_t, ...). S>Именно. Имена только неудачные.
Имхо, это не самая удачная идея.
E>> Но зачем? S>Как зачем? В первую очередь для документирования этой общности. Вот, например, посмотрел ты на массив, и увидел у него метод GetEnumerator. Посмотрел на список, и увидел у него метод GetEnumerator. Подумал — и зафиксировал интерфейс IEnumerable. Который специфицирует некоторое общее поведение.
E>>Способы обработки PDU могут быть разными. Где-то, например, только поле service_type контролируется, где-то только data_coding. S>Можно плясать от обратного — не от сравнения интерфейсов классов, а от использования. В каком-то контексте есть согласованное использование подмножества методов — бах, выделил их в интерфейс.
В том-то все и дело. Интерфейсы нам нужны для определенного поведения в той или иной задаче. Чем больше разнородных задач, тем больше разнородных интерфейсов нам потребуется. Когда речь идет о PDU какого-либо протокола не нужно забывать, что PDU -- это данные. Т.е. у нас есть одни и те же данные, которые в разных задачах будут требовать для себя разных интерфейсов. Так вот вопрос в том, нужно ли в классы, которые инкапсулируют данные PDU, внедрять разнообразные интерфейсы? И проводить затем периодические рефакторинги этих классов при необходимости добавления новых интерфейсов для новой задачи.
Может быть проще оставить классы PDU в виде простейшего инкапсулятора данных PDU с методами getter-ами/setter-ами и сериализацией/десериализацией? А нужные для конкретной задачи интерфейсы представлять в виде внешних классов с врапперами-реализациями для конкретного типа PDU. Имхо, это более перспективный и гибкий путь.
Но если мы идем по этому пути в C++, то у нас есть выбор из двух альтернатив. Во-первых, обычный полиморфизм:
В C++ оба эти решения, имхо, эквиваленты, если у классов pdu есть нужные методы с одинаковыми сигнатурами. Но второй вариант, с одной стороны, короче и проще, а с другой стороны -- эффективней, т.к. нет вызова виртуальных методов и создания промежуточных объектов-врапперов.
Ну и я считаю, что шаблонная функция handle_source_addr_from_pdu и есть разновидность специфического интерфейса для специфической задачи над простейшей инкапсуляцией PDU.
В C#, насколько я понимаю, у меня есть только один путь -- через обычный полиморфизм, там даже generic-и не потребуются.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, eao197, Вы писали:
E>>Просто, исходя из смысла слов "отсылать" (send) и "вызывать" (call) не следует считать, что "отсылка" == "вызову". А в большинстве языков так и есть, причем изменить ничего нельзя.
VD>Перечисли, пожалуйста, языки используещие ассинхронные механимы передачи сообщений.
VD>Даю подсказку, Смолток, откуда ты выдрал эту терминалогию посылает сообщения синхронно.
Надо уточнять — Smalltalk-72 посылал сообщения асинхронно.
Здравствуйте, eao197, Вы писали: E>Нет, мне больше нравится другой подход: не каждый метод приспособлен для того, чтобы исполнятся асинхронно. E>Хотя бы из-за необходимости синхронизации доступа к атрибутам объекта.
Хм. Я неявно предполагал, что каждый объект исполняется в собственном потоке, и синхронизовывать ему нечего. Закончил обрабатывать одно сообщение — получил другое. E>Вовсе не обязательно. E>Вот, например, такая схема. У нас есть объекты A, B, C и D. Каждый работает на свой нити (активные объекты). Объект A отсылает сообщение a объектам B и C. После завершения обработки этого сообщения объект B отсылает сообщение b, а объект C -- сообщение c. Эти сообщения должны поступить объекту D, причем сначала b, затем c. Сложность в том, что B может заканчивать свою обработку после объекта C.
E>Для начала будем считать, что цепочка операций A (a) -> B, C; B(b) -> D, C(c) -> D является транзакцией. Если затем объект A породит следуюшее сообщение a (a2), то эта же цепочка повторится в виде новой транзакции: A(a2) -> B, C; B(b2) -> D, C(c2) -> D.
E>Для поддержки такой синхронизации сообщений bi, ci нам нужен некий монитор транзакции Mi (где i -- номер транзакции). Пусть этот монитор будет представлять из себя следующий конечный автомат: E>Собственно все. Объекты A, B, C, D работают на своих контекстах. Мониторам Mi собственный контекст не нужен, т.к. они вступают в дело только в редкие моменты времени (для их активации можно либо использовать контекст либого из активных объектов, либо отдельный контекст для не активных /пассивных/ объектов).
Ага. А монитор, значить, вызывается синхронно. При этом в нем есть еще и встроенные средства защиты его ресурсов от конкурентного доступа.
В сиомеге такую штуку реализовали. Там все эти конечные автоматы генерируются компилятором. И именно так все и работает — ты вызвал метод, и он сразу же к тебе вернул управление, хотя никаких действий не было выполнено. А будет выполнено действие только тогда, когда кто-то вызовет соответствующий ему метод рандеву. В общем, довольно грамотно все завернуто. По крайней мере, потокобезопасная очередь получается вообще без объявления полей только пара методов.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, VladD2, Вы писали:
E>>Просто, исходя из смысла слов "отсылать" (send) и "вызывать" (call) не следует считать, что "отсылка" == "вызову". А в большинстве языков так и есть, причем изменить ничего нельзя.
VD>Перечисли, пожалуйста, языки используещие ассинхронные механимы передачи сообщений.
Я не знаю таких языков.
Но в языках, где вызов метода рассматривается как отсылка сообщения (Ruby, может быть, Smalltalk) существует возможность перехвата вызова любого метода. А это делает возможным при перехвате сохранить параметры и передать их на выполнение в другой поток. Но сам, исходный вызов в коде не будет отличаться от вызова обычного метода.
Вот например:
class Test < AsyncBase
# Для объявления асинхронного метода используем async вместо def.
async :hello do
puts "hello"
end
end
t = Test.new
t.hello
печатает:
hello intercepted
hello
Происходит это благодоря тому, что в базовом классе AsyncBase было немного шаманства с метапрограммированием и перехватом методов:
class AsyncBase
def self.async( method, &blk )
define_method method, &blk
class_eval <<-EOS
alias :pre_async_#{method} :#{method}
def #{method}
puts "#{method} intercepted"
pre_async_#{method}
end
EOS
end
end
Эта реализация просто печатает сообщение о перехвате метода и тут же запускает метод на выполнение. А могла бы инициировать вызов метода на контексте другой нити. Но вызов hello для объекта t ничем не отличается от вызова любого другого метода.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Sinclair, Вы писали:
E>>Нет, мне больше нравится другой подход: не каждый метод приспособлен для того, чтобы исполнятся асинхронно. E>>Хотя бы из-за необходимости синхронизации доступа к атрибутам объекта. S>Хм. Я неявно предполагал, что каждый объект исполняется в собственном потоке, и синхронизовывать ему нечего. Закончил обрабатывать одно сообщение — получил другое.
Это самый простой и не интересный вариант
E>>Вовсе не обязательно. E>>Вот, например, такая схема. У нас есть объекты A, B, C и D. Каждый работает на свой нити (активные объекты). Объект A отсылает сообщение a объектам B и C. После завершения обработки этого сообщения объект B отсылает сообщение b, а объект C -- сообщение c. Эти сообщения должны поступить объекту D, причем сначала b, затем c. Сложность в том, что B может заканчивать свою обработку после объекта C.
E>>Для начала будем считать, что цепочка операций A (a) -> B, C; B(b) -> D, C(c) -> D является транзакцией. Если затем объект A породит следуюшее сообщение a (a2), то эта же цепочка повторится в виде новой транзакции: A(a2) -> B, C; B(b2) -> D, C(c2) -> D.
E>>Для поддержки такой синхронизации сообщений bi, ci нам нужен некий монитор транзакции Mi (где i -- номер транзакции). Пусть этот монитор будет представлять из себя следующий конечный автомат: E>>Собственно все. Объекты A, B, C, D работают на своих контекстах. Мониторам Mi собственный контекст не нужен, т.к. они вступают в дело только в редкие моменты времени (для их активации можно либо использовать контекст либого из активных объектов, либо отдельный контекст для не активных /пассивных/ объектов). S>Ага. А монитор, значить, вызывается синхронно. При этом в нем есть еще и встроенные средства защиты его ресурсов от конкурентного доступа.
Не обязательно. Он так же может быть объектом, реагирующим на сообщения.
S>В сиомеге такую штуку реализовали. Там все эти конечные автоматы генерируются компилятором. И именно так все и работает — ты вызвал метод, и он сразу же к тебе вернул управление, хотя никаких действий не было выполнено. А будет выполнено действие только тогда, когда кто-то вызовет соответствующий ему метод рандеву. В общем, довольно грамотно все завернуто. По крайней мере, потокобезопасная очередь получается вообще без объявления полей только пара методов.
А у нас в SObjectizer-е (это та штука, ссылку на которую я тебе дал) именно обмен сообщениями используется. Т.к. все сделано на C++ без кодогенерации, то отсылка сообщения сильно отличается от простого вызова метода. А хотелось бы как-то так: Re[22]: Следующий язык программирования
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>VladD2,
>> Разница между дженериками и шаблонами заключается в том, что в дженериках те самые контракты можно задать явно при создании дженериков. А в С++ они определяются неявно в момент вопложения шаблона. При этом копилятор С++ просто пораждает код по шаблону, а потом пытается проверить рпзультат на корректность.
ПК>Разница не столько в явном или неявном задании контрактов, сколько в том, какого рода контракты предполагаются или явно задаются там и там. В C# это (по большей части) требование реализовать тот или иной интерфейс. В C++ -- требование к типу обладать некоторой структурой. Concepts это только позволят задавать явно. Наследования от каких-либо интерфейсов для этого нужно не будет. Кроме явного задания concepts ничего от текущей ситуации в C++ не изменится.
Кроме того, что по concept'ам можно будет перегружать.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, eao197, Вы писали: E>>Просто, исходя из смысла слов "отсылать" (send) и "вызывать" (call) не следует считать, что "отсылка" == "вызову". А в большинстве языков так и есть, причем изменить ничего нельзя. VD>Перечисли, пожалуйста, языки используещие ассинхронные механимы передачи сообщений.
Эрланг Общение между потоками там происходит с помощью посылки асинхронных сообщений.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, VladD2, Вы писали:
E>>>Просто, исходя из смысла слов "отсылать" (send) и "вызывать" (call) не следует считать, что "отсылка" == "вызову". А в большинстве языков так и есть, причем изменить ничего нельзя.
VD>>Перечисли, пожалуйста, языки используещие ассинхронные механимы передачи сообщений.
E>Я не знаю таких языков. E>Но в языках, где вызов метода рассматривается как отсылка сообщения (Ruby, может быть, Smalltalk) существует возможность перехвата вызова любого метода. А это делает возможным при перехвате сохранить параметры и передать их на выполнение в другой поток. Но сам, исходный вызов в коде не будет отличаться от вызова обычного метода.
Похожая схема /пока что/ и используется в Croquet, который базируется на Squeak. Там, правда, реч идёт не об синхронности/асинхронности, а об локальном/реплицируемом-удалённо вызове. Если отправка обычного сообщения выглядит как
aReceiver someMessage
то отправка реплицируемого на удалённые машины сообщения производится так:
aReceiver meta someMessage
meta возвращает прокси, который и производит переадресацию сообщения. Такая техника позволяет выбирать режим отправки. Естественно, это применимо и к асинхронным сообщениям. Например в И-нете я надыбал сообщение за 1999г. о применении этой техники для отправки асинхронных сообщений в Squeak.