/**
* Send a message to a User (from the web, from IM, from SMS). The Actor
* will take care of persisting the information in the database.
*
* @param text - the text of the message
* @param src - the source of the message
*/
case class SendMessage(text: String, src: String) extends UserMsg
...
loop {
react {
// The user sends a message containing text and a source. This can
// be from the web, from IM, from SMS, etc.
case SendMessage(text, src) =>
// create a new Message object to be added to the user's local message and sent
// to followers
val msg = Message(text, System.currentTimeMillis, userName, src)
// add to our local messages (keeping only the maxMessages most recent messages)
latestMsgs = (msg :: latestMsgs).take(maxMessages)
// update all our followers (and ourselves) with the message
(this :: followers).foreach(_ ! msg)
// and put it in the database
MsgStore.create.message(text).source(src).who(userId).save
// if the message was autogenerated, then autogenerate another message in 5 or so minutes
if (src == "autogen") autoGen
Какой глубинный смысл в том, чтобы здесь раскрывать содержимое объекта типа SendMessage на локальные переменные text и src. Лучше ли это, например, вот такого непосредственного обращения с объектом SendMessage:
def onSendMessage( m: SendMessage ) {
val msg = Message(m.text, System.currentTimeMillis, userName, m.src)
// add to our local messages (keeping only the maxMessages most recent messages)
latestMsgs = (msg :: latestMsgs).take(maxMessages)
// update all our followers (and ourselves) with the message
(this :: followers).foreach(_ ! msg)
// and put it in the database
MsgStore.create.message(m.text).source(m.src).who(userId).save
// if the message was autogenerated, then autogenerate another message in 5 or so minutes
if (m.src == "autogen") autoGen
}
и если лучше, то чем?
Вопрос вызван вот чем: протоколы обмена сообщениями неизбежно эволюционируют. Это подразумевает не только изменение состава сообщений, но и изменения структуры отдельных сообщений. Самой простой модификацией при этом оказывается добавление новых полей в сообщение. Скажем, SendMessage может быть расширено полями priority и validity_period. Если прикладной код оформлен в виде методов onSendMessage, то подобные расширения протокола на нем не сказываются. В отличии от кода на основе pattern-matching-а (по моему субъективному мнению).
Интересны преимущества именно по отношению к гибридным языкам, вроде Scala, где есть "обычное" ООП (поскольку, если я правильно понимаю, в более фунциональных языках, вроде Erlang и Haskell, ситуация будет/может выглядеть иначе).
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, eao197, Вы писали:
E>В Scala-программе есть фрагменты, аналогичные вот этому: E>
E> case SendMessage(text, src) =>
E>
E>Какой глубинный смысл в том, чтобы здесь раскрывать содержимое объекта типа SendMessage на локальные переменные text и src.
Объясняю. Глубинный смысл не в самом раскрытии, а в том, что мы "поймали" сообщение конкреного типа. Я не знаток скалы по этому буду "выражаться" в терминах Немерла. Так вот если нам нужно получить само значение (сообщение в данном случае), то моы можем сделать так:
| SendMessage(_, _) as msg => SomeHandler(msg)
и продолжить обработку в функции.
Причем это очень примитивный паттерн. В реальной жизни может быть что-то вроде:
| SendMessage("some text", _) as msg => SomeHandler(src)
| SendMessage("another text", _) as msg => AnotherHandler(src)
Есть языки где паттерн-матчинг можно делать на уровне фунций. Это основная масса "родных" ФЯ (клоны ML-я). В них ты можешь описывать "варианты" функций которые "матчат" отдельные образцы. В Скале и Немерле это делать нельзя, так как эти языки так же поддерживают классический ООП и перегрузку функций в стиле С++.
Однако оператор match мало чем отличается от списка подобных фунций. Достаточно посмотреть на него иметнно так и все твои проблемы резко изчезнут.
Ни уж если речь зашла о патерн-матчинге, то приведу пару примеров реального применения. Вот буквально вчерашний ример:
match (pBody)
{
| PExpr.Sequence([PExpr.Literal(Literal.String(str)) as lit]) =>
def expr = MakeStringTemplateExpr(str, lit.Location, ctx);
...
Этот паттер разбирает тело метода и если тело состоит из текстового литерала, то "добывает" из него с одной стороны само текстовое значение, а с друой АСТ-ветку литерала чтобы взять из нее пестположение ветки. Как записать тоже саме средствами канонического ООП я даже представить не могу. Прямая эмуляция на C# выглядела бы примерно так:
PExpr.Sequence seq = pBody as PExpr.Sequence;
if (seq != null)
{
if (seq.body.Length == 1)
{
PExpr.Literal lit = seq.body.Head as PExpr.Literal;
if (lit != null)
{
Literal.String strLit = lit.val as Literal.String;
if (strLit != null)
{
PExpr expr = MakeStringTemplateExpr(strLit.val, lit.Location, ctx);
...
}
}
}
}
Заметь. В обоих случаях обраборка значения передается в отдельную фунцию. Но сравнить этот код ялично я не могу. В превом случае мы имеем дело с декларативным описанием того что нам нужно, а во втором тем самым императивном "как мы вытаскиваем".
Второй вариант не только более громоздок, но и намного более сложен в изучении. Лично я могу в миг прочесть первый паттерн и буду долго разбираться над вторым вариантом.
Как я уже сказал, в ОО-виде я даже не предсавляю себе как такое сделать. Скорее всего кода буде еще больше.
E> Лучше ли это, например, вот такого непосредственного обращения с объектом SendMessage: E>
E>и если лучше, то чем?
А кто тебе произведет этот вызов? Ты как-то замечательно пропустил тот факт, что сам по себе этот метод не вызовется. Кроме самого вызова тебе ведь нужно распознать тип сообщения и передать управление в нужный метод.
Большинство современных библиотек ГУИ решают данный вопрос петем создания огромных фукнции с одним огромным свитчем и кодом распознования сообщний. Вот как выглядит код такого распознователя в ВыньФормс:
[EditorBrowsable(EditorBrowsableState.Advanced), SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x10:
if (this.CloseReason == CloseReason.None)
{
this.CloseReason = CloseReason.TaskManagerClosing;
}
this.WmClose(ref m);
return;
case 0x11:
case 0x16:
this.CloseReason = CloseReason.WindowsShutDown;
this.WmClose(ref m);
return;
case 20:
this.WmEraseBkgnd(ref m);
return;
case 0x18:
this.WmShowWindow(ref m);
return;
case 0x24:
this.WmGetMinMaxInfo(ref m);
return;
case 5:
this.WmSize(ref m);
return;
case 6:
this.WmActivate(ref m);
return;
case 1:
this.WmCreate(ref m);
return;
case 130:
this.WmNCDestroy(ref m);
return;
case 0x84:
this.WmNCHitTest(ref m);
return;
case 0x86:
if (this.IsRestrictedWindow)
{
base.BeginInvoke(new MethodInvoker(this.RestrictedProcessNcActivate));
}
base.WndProc(ref m);
return;
case 0x47:
this.WmWindowPosChanged(ref m);
return;
case 0xa1:
case 0xa4:
case 0xa7:
case 0xab:
this.WmNcButtonDown(ref m);
return;
case 0x112:
this.WmSysCommand(ref m);
return;
case 0x117:
this.WmInitMenuPopup(ref m);
return;
case 0x120:
this.WmMenuChar(ref m);
return;
case 0x125:
this.WmUnInitMenuPopup(ref m);
return;
case 0x211:
this.WmEnterMenuLoop(ref m);
return;
case 530:
this.WmExitMenuLoop(ref m);
return;
case 0x215:
base.WndProc(ref m);
if (base.CaptureInternal && (Control.MouseButtons == MouseButtons.None))
{
base.CaptureInternal = false;
}
return;
case 0x222:
this.WmMdiActivate(ref m);
return;
case 0x231:
this.WmEnterSizeMove(ref m);
this.DefWndProc(ref m);
return;
case 0x232:
this.WmExitSizeMove(ref m);
this.DefWndProc(ref m);
return;
}
base.WndProc(ref m);
}
Причем этот метод только "распределитель нагрузки". Каждый вызваемый Wm-метод напичкан кодом распознования. Например, вот код метода WmClose:
private void WmClose(ref Message m)
{
FormClosingEventArgs e = new FormClosingEventArgs(this.CloseReason, false);
if (m.Msg == 0x16)
{
e.Cancel = m.WParam == IntPtr.Zero;
}
else
{
if (this.Modal)
{
if (this.dialogResult == DialogResult.None)
{
this.dialogResult = DialogResult.Cancel;
}
this.CalledClosing = false;
e.Cancel = !this.CheckCloseDialog(true);
}
else
{
e.Cancel = !base.Validate(true);
if (this.IsMdiContainer)
{
FormClosingEventArgs args2 = new FormClosingEventArgs(CloseReason.MdiFormClosing, e.Cancel);
foreach (Form form in this.MdiChildren)
{
if (form.IsHandleCreated)
{
form.OnClosing(args2);
form.OnFormClosing(args2);
if (args2.Cancel)
{
e.Cancel = true;
break;
}
}
}
}
Form[] ownedForms = this.OwnedForms;
for (int i = base.Properties.GetInteger(PropOwnedFormsCount) - 1; i >= 0; i--)
{
FormClosingEventArgs args3 = new FormClosingEventArgs(CloseReason.FormOwnerClosing, e.Cancel);
if (ownedForms[i] != null)
{
ownedForms[i].OnFormClosing(args3);
if (args3.Cancel)
{
e.Cancel = true;
break;
}
}
}
this.OnClosing(e);
this.OnFormClosing(e);
}
if (m.Msg == 0x11)
{
m.Result = e.Cancel ? IntPtr.Zero : ((IntPtr) 1);
}
if (this.Modal)
{
return;
}
}
if ((m.Msg != 0x11) && !e.Cancel)
{
FormClosedEventArgs args4;
if (this.IsMdiContainer)
{
args4 = new FormClosedEventArgs(CloseReason.MdiFormClosing);
foreach (Form form2 in this.MdiChildren)
{
if (form2.IsHandleCreated)
{
form2.OnClosed(args4);
form2.OnFormClosed(args4);
}
}
}
Form[] ownedForms = this.OwnedForms;
for (int i = base.Properties.GetInteger(PropOwnedFormsCount) - 1; i >= 0; i--)
{
args4 = new FormClosedEventArgs(CloseReason.FormOwnerClosing);
if (ownedForms[i] != null)
{
ownedForms[i].OnClosed(args4);
ownedForms[i].OnFormClosed(args4);
}
}
args4 = new FormClosedEventArgs(this.CloseReason);
this.OnClosed(args4);
this.OnFormClosed(args4);
base.Dispose();
}
}
Этот код получен путем декомпиляции Рефлектором.
Да, конечно, после того как этот код надолблен обработка событий выливается в сущий пустяк (хотя без поддержки со стороны IDE и он бы был неприятен). Нам нужно подключиться к событию и создать его обработчик. В обработчике параметр уже типизированный.
Вот только, к сожалению, очередь Виндовс нетипизированна и без моря крекерного кода ее обрабатывать очень неприятно.
Это приводит к тому, что добавление нового виндовс-сообщения выливается в море геморроя.
Так вот варианты (в Немерле, или алгебраические типы данных в общем) позволяют создать типизированню очередь (или любую другую последовательную струкуру данных такую как список, стек и т.п.) и предоставляют офигительно удобные средства ее разбора.
Мы просто описываем список сообщений как вариант:
variant Msg
{
| Msg1 { некоторые данные }
| Msg2 { некоторые данные }
| Msg3 { некоторые данные }
...
}
и разбираем их матчем. Причем мы может не просто отличать одно сообщение от другого, но и создавать сложные образцы распознающие целые "ситуации".
E>Вопрос вызван вот чем: протоколы обмена сообщениями неизбежно эволюционируют. Это подразумевает не только изменение состава сообщений, но и изменения структуры отдельных сообщений. Самой простой модификацией при этом оказывается добавление новых полей в сообщение. Скажем, SendMessage может быть расширено полями priority и validity_period. Если прикладной код оформлен в виде методов onSendMessage, то подобные расширения протокола на нем не сказываются. В отличии от кода на основе pattern-matching-а (по моему субъективному мнению).
На самом деле если кто-то расширяет список данных сообщения, то крайне важно проверить все места его обработки на предмет того чтобы они учитывали наличие нужных данных. В случае с паттерн-матчингом в большинстве случаев ты получишь сообщение об ошибке и решить, что делать в конкретном случае. Где данные не нужны подставишь подстановочный знак (подчеркивание), где они нужны допишешь код. А вот как раз с постыми классами так непройдет. Ты просто не будешь знать, что забыл обработать данные.
Опять же напомню, что ты волен перенести обработку в методы. Но и там ты скорее всего воспользуешся паттернм матчингом.
Конечно у всего есть обратные стороны. Главной проблемой вариантов является то, что это замкнутый тип данных (в Скале это возможно не так). Это с одной стороны дает отличный контроль, а с другой не позволяет легко расширять систему во внешних модулях. Хотя и тут есть варианты. Например, можно создать абстрактный базовый класс сообщения от которого наследовать конкретные ванианты сообщения:
using System.Console;
abstract class MsgBase { }
variant Msgs1 : MsgBase
{
| A
| B
}
variant Msgs2 : MsgBase
{
| C
| D
}
module Program
{
Main() : void
{
def test(x : MsgBase)
{
| Msgs1.A => WriteLine("A");
| Msgs2.D => WriteLine("D");
| _ => WriteLine("не, а!");
}
test(Msgs1.A());
test(Msgs2.D());
test(Msgs1.B());
_ = ReadLine();
}
}
этот код выведет:
A
D
не, а!
E>Интересны преимущества именно по отношению к гибридным языкам, вроде Scala, где есть "обычное" ООП (поскольку, если я правильно понимаю, в более фунциональных языках, вроде Erlang и Haskell, ситуация будет/может выглядеть иначе).
Ситуация будет таже самая, хотя именно для тебя может она и будет выглядеть иначе. В этих языках можно объявлять список функций для матчинга по одному типу. Но фактчески это ничем не отличается. Можно даже создать макрос который будте эмулировать синтаксис Хаскеля.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, eao197, Вы писали:
E>Слишком много текста и ничего нового.
Да, в русском языке новых букв за это внемя не появилось. Но ты не теряй надежды.
Вообще песня про алюминиевые огруцы — то явно про тебя. Сам задаешь вопросы, и сам же игнорируешь ответы. Продолжай в том же духе. Все ждешь удобного тебе ответа. Ну, так зачем тогда задавать вопросы то, если ответ тебе известен, но он тебя не удовлетворяет?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, VladD2, Вы писали:
VD>Да, в русском языке новых букв за это внемя не появилось. Но ты не теряй надежды.
VD>Вообще песня про алюминиевые огруцы — то явно про тебя. Сам задаешь вопросы, и сам же игнорируешь ответы. Продолжай в том же духе. Все ждешь удобного тебе ответа. Ну, так зачем тогда задавать вопросы то, если ответ тебе известен, но он тебя не удовлетворяет?
Я не несу ответственности за то, что ты везде, даже если темой определено, что это не к месту, пытаешься в тысячный раз рассказать про матчинг АСТ. И ничего по интересующему меня вопросу.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[5]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, eao197, Вы писали:
E>Я не несу ответственности за то, что ты везде, даже если темой определено, что это не к месту, пытаешься в тысячный раз рассказать про матчинг АСТ. И ничего по интересующему меня вопросу.
Что касается примеров, то АСТ это и есть только пример который под рукой. Зачем мне что-то придумывать, когда рядом есть замечательный пример?
Разницы между АСТ или набором сообщений в общем-то нет никакой. Человек умеющий мыслить абстракно поймет это молниеносно.
Варианты удобны везде где есть необходимость обрабатывать списки состоящие из разнородных данных. Это и АСТ, и сообщения в очереди, и многое другое.
Ты же хочешь получить нужные тебе ответ. А понимтаь ничего ты не хочещь. Так что ты на всю жизнь будешь пленником своих заблуждений. Счастливо оставаться в этом плену.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Протоколы, сообщения: что дает паттерн-матчинг?
От:
Аноним
Дата:
08.05.07 05:26
Оценка:
Чем так плохо?
try
{
PExpr.Sequence seq = pBody as PExpr.Sequence;
PExpr.Literal lit = seq.body.Head as PExpr.Literal;
PExpr expr = MakeStringTemplateExpr(lit.val as Literal.String, lit.Location, ctx);
}
catch (Exception) {}
Re: Протоколы, сообщения: что дает паттерн-матчинг?
E>Скажем, SendMessage может быть расширено полями priority и validity_period. Если прикладной код оформлен в виде методов onSendMessage, то подобные расширения протокола на нем не сказываются. В отличии от кода на основе pattern-matching-а (по моему субъективному мнению).
Ну, не отражается на onSendMessage, но ведь отражается где-то на коде, который работает с этим SendMessage и его новыми полями. Тот код придется менять, чтобы принять во внимание новые поля. Так и с паттерн-матчингом. Придется менять код, который должен работать с новыми полями.
Причем следующие два кода ничем не отличаются:
%%
%% Наша структура
%%
-record(sendMessage{text, sender}).
%%
%% Принимаем сообщение типа SendMessage и сразу вытягиваем из него text и sender
%%
send_message_event_handler(#sendMessage{text=Text, sender=Sender}) ->
logger:log("Sender: %s, text: %s", [Sender, Text]).
Если изменится структура SendMessage, то код будет продолжать работать как есть. Если же надо будет использовать и новые поля, то код и там и там поменяется.
Здравствуйте, Mamut, Вы писали:
M>Если изменится структура SendMessage, то код будет продолжать работать как есть. Если же надо будет использовать и новые поля, то код и там и там поменяется.
Далеко не обязательно, что будет продложать работать как есть. Не зря Ulf Wiger выступает против чрезмерного использования record'ов — скрое всего получится, что при обновлении нужно перекомпилять исходники всех клиентов.
Ну невозможно обеспечить обработку изменившихся событий не меня код, который эти события обрабатывает.
Re[2]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, Mamut, Вы писали:
E>>Скажем, SendMessage может быть расширено полями priority и validity_period. Если прикладной код оформлен в виде методов onSendMessage, то подобные расширения протокола на нем не сказываются. В отличии от кода на основе pattern-matching-а (по моему субъективному мнению).
M>Ну, не отражается на onSendMessage, но ведь отражается где-то на коде, который работает с этим SendMessage и его новыми полями. Тот код придется менять, чтобы принять во внимание новые поля. Так и с паттерн-матчингом. Придется менять код, который должен работать с новыми полями.
Не совсем так. В случае показанного в исходном Scala-примере паттерн матчинга изменять придется две вещи:
* матчинг сообщения SendMessage;
* обработку сообщения SendMessage.
Если бы матчинг делался только для сообщения SendMessage (без автоматического извлечения из него полей), то пришлось бы править только обработку SendMessage, а предикат в match остался бы неизменным.
Это становится особенно важно, когда обработка SendMessage делается не по месту (т.е. не прямо в match), а делегируется в какую-нибудь функцию. Которая может находиться даже в другой библиотеке.
Вообще-то говоря, при расширении сообщений в протоколах новыми полями не всегда требуется менять код обработки сообщения. Скажем, если добавляется поле priority, а мы пишем службу ретрансляции, которой на это поле начихать, то изменение SendMessage вообще не скажется на нашем коде. А вот при паттерн-матчинге поле priority придется добавить в предикат, иначе не сматчится.
Еще одна штука, которая проявляется при работе с сообщениями. Иногда атрибуты сообщения либо эмулируются, либо выводятся через содержимое других атрибутов. Скажем, поле name со временем может быть изъято, а метод name останется и будет возвращать конкатенацию атрибутов sender и topic. Код, который работал без автоматической распаковки поля name в паттерн-матчинге продолжит работать, а вот если распаковка поля name была, то ее придется изменять.
Пока я думаю, что в примере, к которому я доколупался, просто пошли по пути наименьшего сопротивления -- демо и есть демо, чего изобретать велосипед.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, eao197, Вы писали:
E>Интересны преимущества именно по отношению к гибридным языкам, вроде Scala, где есть "обычное" ООП (поскольку, если я правильно понимаю, в более фунциональных языках, вроде Erlang и Haskell, ситуация будет/может выглядеть иначе).
Лично мне кажется, что паттерн матчинг удобнее применять там, где у нас постоянный (или почти постоянный) набор сообщений, а вот обработчики (или, точнее, ситуации обработки) регулярно меняются и добавляються.
Чтобы было понятно, что я имею в виду, приведу пример. Пускай у нас есть 2 сообщения и 2 ситуации. Напишем для них тривиальную логику:
Java
iterface Message {
// Это для обработчика 1void doSome1();
// А это для второгоvoid doSome2();
}
class Mess1 implements Message {
public Mess1(int value) {
this.value = value;
}
private int value;
public void doSome1() {
System.out.println(value);
}
public void doSome2() {
System.out.println(value + 5);
}
}
class Mess2 implements Message {
public void doSome1() {
System.out.println("Съешь еще французких булочек да выпей чаю");
}
public void doSome2() {
System.out.println("Чай кончился :(");
}
}
interface Handler {
void handle(Message m);
}
class Handler1 implements Handler {
public void handle(Message m) {
m.doSome1();
}
}
class Handler2 implements Handler{
public void handle(Message m) {
m.doSome2();
}
}
Перепишем тривиальную логику для Scala+PM
abstract class Message;
case class Mess1(int value);
case object Mess2;
trait Handler {
def handle(Message m): Unit;
}
object Handler1 extends Object with Handler {
def handle(Message m) = m match {
case Mess1(v) => Console.println(v)
case Mess2 => Console.println("Съешь еще французких булочек да выпей чаю")
}
}
object Handler2 extends Object with Handler {
def handle(Message m) = m match {
case Mess1(v) => Console.println(v + 5)
case Mess2 => Console.println("Чай кончился :(")
}
}
А теперь попробуем добавить новое сообщение. В ООП варианте это проще — нужно просто заимплементить интерфейс, в обработчики изменения вносить не нужно. В ПМ-варианте же нужно вносить изменения во все матчеры.
С другой стороны, попробуем добавить новый хэндлер. ПМ-вариант выигрывает — нужно написать новый матчер, где разобрать все виды сообщений. Просто и со вкусом. В ООП-варианте же придется создавать новый метод и имплементить его во всех классах.
ИМХО, при выборе Strategy/State vs PM нужно посмотреть на ситуацию именно с такой стороны. Спасибо за потраченное время
ЗЫ А еще можно подумать о визиторе, хотя, ИМХО, перед он меркнет перед красотой ПМ
<< RSDN@Home 1.1.4 stable SR1 rev. 568>>
Сейчас играет Haggard — Eppur Si Muove
Re[2]: Протоколы, сообщения: что дает паттерн-матчинг?
Пара соображений, если не возражаешь.
G>Чтобы было понятно, что я имею в виду, приведу пример. Пускай у нас есть 2 сообщения и 2 ситуации. Напишем для них тривиальную логику: G>Java G>
G>iterface Message {
G> // Это для обработчика 1
G> void doSome1();
G> // А это для второго
G> void doSome2();
G>}
G>
Имхо, не есть хорошо помещать в сообщения какие-либо обработчики. Конечно, если речь идет о сообщениях какого-нибудь протокола взаимодействия приложений. В некоторых случаях в сообщения имеет смысл помещать код собственной валидации -- удобно, сразу после десериализации сообщения вызывается валидатор и, если он обнаруживает какую-нибудь проблему, сообщение отвергается. Но в некоторых случаях даже такие валидаторы должны быть реализованны отдельно от сообщения, а сообщение только инкапсулирует в себе собственные данные.
G>А теперь попробуем добавить новое сообщение. В ООП варианте это проще — нужно просто заимплементить интерфейс, в обработчики изменения вносить не нужно. В ПМ-варианте же нужно вносить изменения во все матчеры. G>С другой стороны, попробуем добавить новый хэндлер. ПМ-вариант выигрывает — нужно написать новый матчер, где разобрать все виды сообщений. Просто и со вкусом. В ООП-варианте же придется создавать новый метод и имплементить его во всех классах. G>ИМХО, при выборе Strategy/State vs PM нужно посмотреть на ситуацию именно с такой стороны. Спасибо за потраченное время
авторы Scala рассматривают разные способы использования PM и других способов матчинга объектов. И приводят соображения о том, в какой ситуации какой способ предпочтительнее. И эффективнее, что иногда важно.
G>ЗЫ А еще можно подумать о визиторе, хотя, ИМХО, перед он меркнет перед красотой ПМ
У визитора перед ПМ есть еще одно интересное преимущество -- при добавлении нового сообщения в протокол в визитор обязательно добавляется новый абстрактный метод, который обязательно должен получить конкретную реализацию. Т.е. код по обработке старой версии протокола просто не скомпилируется. В ПМ же придется в динамике проверять реацию на новые сообщения. Например, для тех же валидаторов входящих сообщений схема с абстрактными методами визиторов является еще одним уровнем защиты Такая вот извечная дилема статика/динамика. Хотя, определенно, будут находится ситуации, когда ПМ даже в этих случаях удобнее (например, если новые сообщения нужно просто игнорировать).
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[3]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, eao197, Вы писали:
E>У визитора перед ПМ есть еще одно интересное преимущество -- при добавлении нового сообщения в протокол в визитор обязательно добавляется новый абстрактный метод, который обязательно должен получить конкретную реализацию. Т.е. код по обработке старой версии протокола просто не скомпилируется. В ПМ же придется в динамике проверять реацию на новые сообщения. Например, для тех же валидаторов входящих сообщений схема с абстрактными методами визиторов является еще одним уровнем защиты Такая вот извечная дилема статика/динамика. Хотя, определенно, будут находится ситуации, когда ПМ даже в этих случаях удобнее (например, если новые сообщения нужно просто игнорировать).
Компиляторы и Haskell, и Ocaml умеют предупреждать о неполном/избыточном наборе паттернов. Компилятор Скалы, предполагаю, тоже.
E>Пока я думаю, что в примере, к которому я доколупался, просто пошли по пути наименьшего сопротивления -- демо и есть демо, чего изобретать велосипед.
Скорее всего так и есть. Просто без грамотного проектирования наломать дров можно вне зависимоти от основной парадигмы языка
PM>Компиляторы и Haskell, и Ocaml умеют предупреждать о неполном/избыточном наборе паттернов. Компилятор Скалы, предполагаю, тоже.
Проверил, таки тоже умеет:
scala> def foo (x : Option[int]) = x match { case None => 5 }
console>:4: warning: does not cover case {class Some}
def foo (x : Option[int]) = x match { case None => 5 }
Re[5]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, palm mute, Вы писали:
PM>>Компиляторы и Haskell, и Ocaml умеют предупреждать о неполном/избыточном наборе паттернов. Компилятор Скалы, предполагаю, тоже. PM>Проверил, таки тоже умеет: PM>
scala>> def foo (x : Option[int]) = x match { case None => 5 }
PM>console>:4: warning: does not cover case {class Some}
PM> def foo (x : Option[int]) = x match { case None => 5 }
PM>
Я вот о чем. Пусть у нас сначала был протокол из 4-х сообщений: HandleTrx, HandleTrxReply, Ping и Pong. И все у нас замечательно работало:
abstract class Msg;
case class HandleTrx extends Msg;
case class HandleTrxReply extends Msg;
case class Ping extends Msg;
case class Pong extends Msg;
object Demo extends Application
{
def handleMsg( m: Msg ) {
m match {
case HandleTrx() => Console.println( "HandleTrx" )
case HandleTrxReply() => Console.println( "HandleTrxReply" )
case Ping() => Console.println( "Ping" )
case Pong() => Console.println( "Pong" )
}
}
handleMsg( new HandleTrx )
handleMsg( new Ping )
handleMsg( new HandleTrxReply )
}
Потом мы решили расширить протокол парой новых сообщений: SetupThroughput и SetupThroughputReply. И здесь уже нас не предупреждают, что handleMsg не обрабатывает их:
abstract class Msg;
case class HandleTrx extends Msg;
case class HandleTrxReply extends Msg;
case class Ping extends Msg;
case class Pong extends Msg;
case class SetupThroughput extends Msg;
case class SetupThroughputReply extends Msg;
object Demo extends Application
{
def handleMsg( m: Msg ) {
m match {
case HandleTrx() => Console.println( "HandleTrx" )
case HandleTrxReply() => Console.println( "HandleTrxReply" )
case Ping() => Console.println( "Ping" )
case Pong() => Console.println( "Pong" )
}
}
handleMsg( new HandleTrx )
handleMsg( new Ping )
handleMsg( new HandleTrxReply )
handleMsg( new SetupThroughput )
}
В результате, совершенно естественно мы получаем по мозгам в run-time:
HandleTrx
Ping
HandleTrxReply
java.lang.ExceptionInInitializerError
at Demo.main(Demo.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:76)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:106)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
Caused by: scala.MatchError: SetupThroughput()
at Demo$.handleMsg(Demo.scala:14)
at Demo$.<init>(Demo.scala:25)
at Demo$.<clinit>(Demo.scala)
... 8 more
Чесно говоря, я не вижу возможностей для Scala информировать меня о неполных case в match, т.к. новые case-классы могут быть добавлены уже после компиляции объекта Demo, в другом модуле, другим разработчиком.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[6]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, eao197, Вы писали:
E>Я вот о чем. Пусть у нас сначала был протокол из 4-х сообщений: HandleTrx, HandleTrxReply, Ping и Pong. И все у нас замечательно работало: E>
E>abstract class Msg;
E>case class HandleTrx extends Msg;
E>case class HandleTrxReply extends Msg;
E>case class Ping extends Msg;
E>case class Pong extends Msg;
...
E>Потом мы решили расширить протокол парой новых сообщений: SetupThroughput и SetupThroughputReply. И здесь уже нас не предупреждают, что handleMsg не обрабатывает их: E>
E>abstract class Msg;
E>case class SetupThroughput extends Msg;
E>case class SetupThroughputReply extends Msg;
...
E>
В данном случае достаточно sealed abstract class Msg: http://www.nabble.com/-scala---compiler-verification-of-exhaustive-matching-t3625118.html
Тогда предупреждения будут.
E>Чесно говоря, я не вижу возможностей для Scala информировать меня о неполных case в match, т.к. новые case-классы могут быть добавлены уже после компиляции объекта Demo, в другом модуле, другим разработчиком.
В случае sealed базового класса — не могут. И согласись, в случае Посетителя новые сообщения в другом модуле другим разработчиком тоже не добавляются.
Re[7]: Протоколы, сообщения: что дает паттерн-матчинг?
Ага, есть.
E>>Чесно говоря, я не вижу возможностей для Scala информировать меня о неполных case в match, т.к. новые case-классы могут быть добавлены уже после компиляции объекта Demo, в другом модуле, другим разработчиком.
PM>В случае sealed базового класса — не могут. И согласись, в случае Посетителя новые сообщения в другом модуле другим разработчиком тоже не добавляются.
Да.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[3]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, <Аноним>, Вы писали:
А>Чем так плохо?
А>
А>try
А>{
А> PExpr.Sequence seq = pBody as PExpr.Sequence;
А> PExpr.Literal lit = seq.body.Head as PExpr.Literal;
А> PExpr expr = MakeStringTemplateExpr(lit.val as Literal.String, lit.Location, ctx);
А>}
А>catch (Exception) {}
А>
1. Это неверный код. Он пропутит список содержащий более одного элемента.
2. За программирование на исключениях надо отдельно морду бить. Обоенно этот стиль хорош в отладке. Никогда не знаешь глючит программа или исключения летят шатно. О скорости тут и говорить не приходится.
3. Ну, и главное. Все равно получается 7 строк кода вместо двух. И опять же их прийдется распознавать, а не читать. В этих приведениях типов черт ногу сломит.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Протоколы, сообщения: что дает паттерн-матчинг?
Здравствуйте, Gajdalager, Вы писали:
G>А теперь попробуем добавить новое сообщение. В ООП варианте это проще — нужно просто заимплементить интерфейс, в обработчики изменения вносить не нужно. В ПМ-варианте же нужно вносить изменения во все матчеры.
Поясни, плиз, чем отличается добавление реализации метода от добавления case-а?
И там и там тебе прийдется добавить обработчик. И там, и там компилятор тыкнет тебя носом в место где нужно добавить код. Причем если ты зохочешь, то сможешь сделать обработчик по-умолчанию который будет обрабатывать не обработаные сообщения.
На мой взгляд ты ошибашся на счет простоты. Код на Яве уже значительно больше, сложнее и непонятнее. Этого уже достаточно чтобы не писать так.
Так что единственная предпосылка против паттерн-матчинга — это возможность обработки сообщений во внешних модулях при словии изменения списка сообщений. Вот тут конечно паттерн-матчинг никуда не годен. Он создает жесткую связь с конкретной версией "сервера". При изменении сервера прийдется перекомпилировать и всех клиентов. Вот только решение на Яве тут ничем не отличается.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.