Собственно сабж.
Много читал про то, что в ряде языков мультиметоды реализуются в самом языке (CLOS, Cecil, Dylan, Self), только разбираемые примеры выглядят несколько искуственными с той точки зрения, что подобные задачи (когда результат определяется как некое "пересечение" 2-х и более типов объектов) встречаются, на мой взгляд, относительно редко. Поэтому есть мысль, что, несмотря на крутость "встроенности" мультиметодов, в реальной жизни они не так часто дают выгоду, т.к. являются интересной фенечкой лишь иногда позволяющей изящно решить задачу.
Здравствуйте, Курилка, Вы писали:
К>Собственно сабж. К>Много читал про то, что в ряде языков мультиметоды реализуются в самом языке (CLOS, Cecil, Dylan, Self), только разбираемые примеры выглядят несколько искуственными с той точки зрения, что подобные задачи (когда результат определяется как некое "пересечение" 2-х и более типов объектов) встречаются, на мой взгляд, относительно редко. Поэтому есть мысль, что, несмотря на крутость "встроенности" мультиметодов, в реальной жизни они не так часто дают выгоду, т.к. являются интересной фенечкой лишь иногда позволяющей изящно решить задачу.
Если для вас задача "что будет если всепробивающее ядро попадет в непрошибаемый столб" жизненно важна, но мультиметоды помогут. Вообще наверное пролезно в играх — там дамажи всякие по юнитам разным — можно сделать лучше чем у близзарда в варкрафте.
У меня был лично еще такой пример — делал очередь операций (Undo/Redo), так вот — некоторые операции — например перемещение объекта, было бе не плохо склеить в одну (потеряв информацию промежуточном состоянии за ненадобностью) и сэкономить памят и все такое. Жутко интерсено узнавать можно ли склеить 2 операции и что получиться в результате. классов операций было около 50 так что скучно все без ММ
Представь себе, что у тебя есть набор примитивных объектов, над которыми надо делать, например, операцию сложения. При этом объекты разного типа можно складывать, но правила сложения разные. Например, есть у тебя .NET BCL и в ней есть int, double, DateTime, TimeSpan, которые все между собой замечательно складываются (int + double => double, int + DateTime => DateTime, double + TimeSpan => TimeSpan). Писать такой метод сложения без мультиметодов напряжно — приходится их "реализовывать" через reflection и хеш-таблицы.
Привёл такой пример потому, что в форуме dotnet он пробегал как часть реального проекта.
В общем-то, всему найдётся реальное применение, просто иногда с ходу тяжело его назвать, если сам не сталкивался.
Здравствуйте, Oyster, Вы писали:
O>Здравствуйте, Курилка, Вы писали:
O>Представь себе, что у тебя есть набор примитивных объектов, над которыми надо делать, например, операцию сложения. При этом объекты разного типа можно складывать, но правила сложения разные. Например, есть у тебя .NET BCL и в ней есть int, double, DateTime, TimeSpan, которые все между собой замечательно складываются (int + double => double, int + DateTime => DateTime, double + TimeSpan => TimeSpan). Писать такой метод сложения без мультиметодов напряжно — приходится их "реализовывать" через reflection и хеш-таблицы.
O>Привёл такой пример потому, что в форуме dotnet он пробегал как часть реального проекта.
O>В общем-то, всему найдётся реальное применение, просто иногда с ходу тяжело его назвать, если сам не сталкивался.
Вопрос не в том, есть ли применение вообще, а скорее насколько такие задачи часто встречаются
Здравствуйте, Курилка, Вы писали:
К>Вопрос не в том, есть ли применение вообще, а скорее насколько такие задачи часто встречаются
Имхо всё зависит от конкретного проекта — у меня вот ни разу не было необходимости. В общем, если её нет, то можно сэмулировать средствами языка (как правило) или реализовать по другому, если есть — нехай будет
С другой стороны, возможно, я просто не умею её готовить и поэтому не использую, хотя мог бы.
Здравствуйте, Курилка, Вы писали:
К>Собственно сабж. К>Много читал про то, что в ряде языков мультиметоды реализуются в самом языке (CLOS, Cecil, Dylan, Self), только разбираемые примеры выглядят несколько искуственными с той точки зрения, что подобные задачи (когда результат определяется как некое "пересечение" 2-х и более типов объектов) встречаются, на мой взгляд, относительно редко. Поэтому есть мысль, что, несмотря на крутость "встроенности" мультиметодов, в реальной жизни они не так часто дают выгоду, т.к. являются интересной фенечкой лишь иногда позволяющей изящно решить задачу.
они позволяют, например, избавляться от уродливых конструкций вида
if (x is TypeA)
{
....
}
else if (x is TypeB)
{
....
}
....
визиторы, кстати, это один из частных случаев мультиметодов — по своей сути эмуляция двойной диспетчеризации
Здравствуйте, Дарней, Вы писали:
Д>они позволяют, например, избавляться от уродливых конструкций вида Д>
Д>if (x is TypeA)
Д>{
Д>....
Д>}
Д>else if (x is TypeB)
Д>{
Д>....
Д>}
Д>....
Д>
А теперь к моему вопросу — насколько это востребованно?
Д>визиторы, кстати, это один из частных случаев мультиметодов — по своей сути эмуляция двойной диспетчеризации
Здравствуйте, Курилка, Вы писали:
К>А теперь к моему вопросу — насколько это востребованно?
подумал я тут над этим вопрос, и пришел к выводу — мультиметоды у меня обычно востребованы, когда нужно строить сложные DOM'ы и операции над их элементами. Сюда можно также включить деревья контролов в гуе.
Помимо этого, мультиметоды редко когда нужны.
Здравствуйте, Курилка, Вы писали:
К>Много читал про то, что в ряде языков мультиметоды реализуются в самом языке (CLOS, Cecil, Dylan, Self), только разбираемые примеры выглядят несколько искуственными с той точки зрения, что подобные задачи (когда результат определяется как некое "пересечение" 2-х и более типов объектов) встречаются, на мой взгляд, относительно редко. Поэтому есть мысль, что, несмотря на крутость "встроенности" мультиметодов, в реальной жизни они не так часто дают выгоду, т.к. являются интересной фенечкой лишь иногда позволяющей изящно решить задачу.
Так как 99% языков без них обходятся, то и говорить не о чем. К тому же как я понимаю, фишки языка вроде сопоставления с образцом могут очень достойно заменять мультиметоды. Вот простенький пример на Нэмерле:
using System.Console;
class A { }
[Record]
class B : A { public Y : string; }
[Record]
class C : A { public Z : int; }
def Func(a1 : A, a2 : A) : void
{
match ((a1, a2))
{ | (_ is B, _ is C) => WriteLine(":)");
| (_ is C, _ is B) => WriteLine(":|");
| _ => WriteLine(":(");
}
}
Func(B("Test 1"), C(2));
Func(C(2), B("Test 2"));
Func(C(2), A());
Этот код выводит:
:)
:|
:(
что аналогично поведению мультиметодов.
Или вариация с использованием значений:
using System.Console;
class A { }
[Record]
class B : A { public Y : string; }
[Record]
class C : A { public Z : int; }
// Здесь используется неявное сопоставление с обрзцом.
def Func(a1 : A, a2 : A) : void
{ | (b is B, c is C) => WriteLine($"a1.Y='$(b.Y)' a2.Z=$(c.Z)");
| (c is C, b is B) => WriteLine($"a1.Z=$(c.Z) a2.Y='$(b.Y)'");
| _ => WriteLine(":(");
}
Func(B("Test 1"), C(2));
Func(C(2), B("Test 2"));
Func(C(2), A());
выводит:
a1.Y='Test 1' a2.Z=2
a1.Z=2 a2.Y='Test 2'
:(
А вот что получается если декомпилировать код в C#:
private static void _N_Func1893(A a1, A a2)
{
Tuple<A, A> tuple1 = new Tuple<A, A>(a1, a2);
if (!(tuple1.field0 is B))
{
if (!(tuple1.field0 is C))
{
goto Label_0093;
}
C c1 = (C) tuple1.field0;
if (!(tuple1.field1 is B))
{
goto Label_0093;
}
B b1 = (B) tuple1.field1;
StringBuilder builder2 = new StringBuilder();
builder2.Append("a1.Z=");
builder2.Append(c1.Z);
builder2.Append(" a2.Y='");
builder2.Append(b1.Y);
builder2.Append("'");
Console.WriteLine(builder2.ToString());
return;
}
B b2 = (B) tuple1.field0;
if (tuple1.field1 is C)
{
C c2 = (C) tuple1.field1;
StringBuilder builder1 = new StringBuilder();
builder1.Append("a1.Y='");
builder1.Append(b2.Y);
builder1.Append("' a2.Z=");
builder1.Append(c2.Z);
Console.WriteLine(builder1.ToString());
return;
}
Label_0093:
Console.WriteLine(":(");
}
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Курилка, Вы писали:
К>А теперь к моему вопросу — насколько это востребованно?
Все зависит от задачи.
Например, если задача "распознать" дерево, то можно:
1. Восспользоваться ООП и паттерном Посетитель.
2. Воспользоваться сопоставлением с образцом.
3. Воспользоваться мултиметодами.
Думаю, что вариант 2 и 3 проще в реализауии, более интуитивны (не нужно разглядывать паттерн в его ерализации) и компактнее.
Одако и с Посетителем можно с успехом жить.
Использвать же "if (x is Y)" в ОО-языках крайне не удобно.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Так как 99% языков без них обходятся, то и говорить не о чем. К тому же как я понимаю, фишки языка вроде сопоставления с образцом могут очень достойно заменять мультиметоды. Вот простенький пример на Нэмерле:
Нюанс! Полиморфная функция может быть определена вся в одном месте, либо размазана по системе.
Сопоставление с образцом — это первый случай, а виртуальные функции — второй.
Естественно, тут у обоих подходов есть и сильные, и слабые стороны.
Особенность мультиметодов — в том, что, если не определены все возможные комбинации типов, то возникает неоднозначность, по какой ветке пойти в случае неточного совпадения.
Эту неоднозначность разрешают разными способами.
Самые распространённые
— порядок, определённый пользователем (таблица сопоставления с образцами, собранная вручную)
— лексикографический порядок (двойная диспетчеризация; автоматически заполненная таблица)
На ёлку влезть и рыбку съесть — не получается. Либо мы вводим устраивающий нас порядок и платим за это плохой масштабируемостью и сопровождаемостью (попробуйте отладить таблицу сопоставления, если в иерархиях по 50 классов). Либо подстраиваемся под жёстко прибитый порядок, получая внятную логику и возможность масштабирования.
А фишки языка... они большого значения не имеют. Проблема-то именно архитектурная.
Даже в С++ можем нагородить if(dynamic_cast<X*>(a) && dynamic_cast<Y*>(b))... или что-нибудь подобное. Ну там макросами оформить. В конце концов, MFC строит таблицы виндовских сообщений (кстати, тоже вариация на тему мультиметодов) макросами, и все счастливы.
Ну вот у меня из недавнего. Есть некий движок-интерпретатор, и есть некое множество арифметических операций над объектами числами. Все бы ничего, да вот только типов этих чисел очень много:
— обычные целые
— обычные с плавающей точкой
— "длинные" целые (не int64, а именно бесконечной длины)
— дроби (т.е. число, представленное как пара: числитель/знаменатель)
Соответственно, при арифметических действиях алгоритм и сам результат зависит от взаимных типов операндов. Естественно здесь применить двойную диспечеризацию, но на мультиметодах запись была бы эстетичнее, что ли...
Здравствуйте, Кодт, Вы писали:
К>Естественно, тут у обоих подходов есть и сильные, и слабые стороны.
+1
К>Особенность мультиметодов — в том, что, если не определены все возможные комбинации типов, то возникает неоднозначность, по какой ветке пойти в случае неточного совпадения. К>Эту неоднозначность разрешают разными способами. К>Самые распространённые К>- порядок, определённый пользователем (таблица сопоставления с образцами, собранная вручную) К>- лексикографический порядок (двойная диспетчеризация; автоматически заполненная таблица)
Кстати, твои слова навели на мысль, что мультиметоды вообще мало применимы в компонентных стистемах, так как они могут содержаться в разных модуля, в том числе и подгружаемых динамически. Это приведет или к неверному поведению, или (при динамической реструктуризации) к нехилым проблемам с логическими ошибками. Так что я уже и не хочу связываться с мультиметодами. Все же то, что код сопоставления в одном месте дает детерминированность. Гибкость мультиметодов может выйти боком.
К>А фишки языка... они большого значения не имеют. Проблема-то именно архитектурная.
Дык если есть сопоставление с образцом, то мы имеем средство для решения большинства задачь без сильных побочных эффектов. А что еще надо?
К>Даже в С++ можем нагородить if(dynamic_cast<X*>(a) && dynamic_cast<Y*>(b))... или что-нибудь подобное. Ну там макросами оформить. В конце концов, MFC строит таблицы виндовских сообщений (кстати, тоже вариация на тему мультиметодов) макросами, и все счастливы.
Счастливы только маньяки или психи. Тот кто видел полноценную компонентную систему построения GUI вряд ли испытает щастье от левой эмуляции на макросах.
Кстати, о чем в MFC ты говоришь как о мультиметодах? О картах сообщений?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VladD2 wrote: > Счастливы только маньяки или психи. Тот кто видел полноценную > компонентную систему построения GUI вряд ли испытает щастье от левой > эмуляции на макросах. > Кстати, о чем в MFC ты говоришь как о мультиметодах? О картах сообщений?
Ага. Классическая двойная диспетчеризация — по типу аргумента (коду
сообщения) и по указателю 'this'.
VladD2 wrote: > C>Ага. Классическая двойная диспетчеризация — по типу аргумента (коду > C>сообщения) и по указателю 'this'. > Хм. Но сообщение является целочисленной константой. То есть второй > параметр не полиморфен. Так что это обычный вызов.
Ну представь, что вместо WM_SETTEXT у тебя объект SetTextMessageData,
унаследованый от MessageData.
> C>Ага. Классическая двойная диспетчеризация — по типу аргумента (коду > C>сообщения) и по указателю 'this'. > > Хм. Но сообщение является целочисленной константой. То есть второй параметр не полиморфен. Так что это обычный вызов.
Это всё-таки полиморфный вызов с вариантным аргументом. Ну а то, что это всё реализовано на единственной точке входа и целочисленных параметрах — ну так винапи это же, считай, ассемблер.
Здравствуйте, Курилка, Вы писали:
Д>>визиторы, кстати, это один из частных случаев мультиметодов — по своей сути эмуляция двойной диспетчеризации
К>Не спорю
Не согласен. Визитор просто позволяет вынести виртуальный метод из иерархии классов в один отдельный класс.
Нет, его конечно можно назвать мультиметодом для объектов (тип действия, класс), но это называется просто методом. Диспетчерезацию по типу метода всегда выполняет сам программист, когда после точки пишет имя метода. Собственно глупо пытаться выполнить какое-то абстрактное действие, не указав, какое конкретно действие хочешь выполнить.
Здравствуйте, Курилка, Вы писали:
К>Собственно сабж. К>Много читал про то, что в ряде языков мультиметоды реализуются в самом языке (CLOS, Cecil, Dylan, Self), только разбираемые примеры выглядят несколько искуственными с той точки зрения, что подобные задачи (когда результат определяется как некое "пересечение" 2-х и более типов объектов) встречаются, на мой взгляд, относительно редко. Поэтому есть мысль, что, несмотря на крутость "встроенности" мультиметодов, в реальной жизни они не так часто дают выгоду, т.к. являются интересной фенечкой лишь иногда позволяющей изящно решить задачу.
Я думаю так: сейчас мультиметоды не очень нужны, но только потомучто они промушленными языками не поддержтваются, а как следствие люди не только мало чего про них знают, но и просто не умеют их выделять. Т.е. смотрят на какой-то код, но не понимают, что фактически имеют дело с мультиметодами.
Я, когда первый раз читал классический пример про пересечение фигур, думал "о чём вообще ребята говорят, я про это не знаю и мне без этого нормально живётся"
Примеров много. Фактически, когда у тебя есть не N действий, а N*M действий в системе — это мультиметод.
Далеко ходить не надо — N типов GUI окон * M типов событий — получается N*M действий. Да это мультиметод, несмотря на то, что мало кто об этом задумывается.
С поддержкой в языке многие вещи стали бы проще (кстати, есть предложение по включению в c++ мультиметодов). Сейчас с ними такая же ситуация, как с динамическим полиморфизмом во время С, когда в языке не было поддержки. Т.е. — реализовывали вручную + многие даже не знали как это называется, просто приходили к такому решению.
Я об этом задумался, когда сам споткнулся на плёвой ситуации — попытался реализовать publisher/subscriber на с++ — не получается. Думаю, чо за фигня — не получается реализовать такую простую вещь. Нет, можно конечно, но для этого надо в интерфейсе ISubscriber сделать по методу для каждого типа события:
То, что в Action1 надо реализовывать dispatch, это меня ещё устраивало — это можно сделать на шаблонах, но то что в ISubscriber надо прописать по методы на каждое событие, меня не устраивало. Т.к. система задумывалась как расширяемая.
И сейчас в промышленных языках (С++/С#/Java) эту задачу просто не решить (я имею в виду, что решить, но извращаться нужно).
Если бы была поддержка со стороны языка — это бы решалось элементарно — так же как и динамический полиморфизм.
Кстати, если вернуться к MFC и обработке событий окнами. Сейчас мы имеем очень кривую систему — все эти макросы, switch, LPARAM, WPARAM — бред. Если бы в с++ была поддержка мультиметодов, то возможно мы писали бы просто:
Т.е. — не надо карт событий, не надо всех этих reinterpret_cast, динамического создания/удаления параметров события. Всё было бы красиво, типобезопасно и автоматически.