Протоколы, сообщения: что дает паттерн-матчинг?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.05.07 09:25
Оценка:
Возвращаясь к спору о примере использования паттерн-матчинга для поддержки обработки сообщений между актерами в Scala, начатому с вот этого сообщения
Автор: eao197
Дата: 03.05.07
.

В Scala-программе есть фрагменты, аналогичные вот этому:
/**
* 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: Протоколы, сообщения: что дает паттерн-матчинг?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.05.07 18:30
Оценка:
Здравствуйте, 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>def onSendMessage( m: SendMessage ) {
E>  ...
E>}
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 Беларусь http://eao197.blogspot.com
Дата: 07.05.07 18:44
Оценка: :)
Здравствуйте, VladD2, Вы писали:

Слишком много текста и ничего нового.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[3]: Протоколы, сообщения: что дает паттерн-матчинг?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.05.07 18:48
Оценка:
Здравствуйте, eao197, Вы писали:

E>Слишком много текста и ничего нового.


Да, в русском языке новых букв за это внемя не появилось. Но ты не теряй надежды.

Вообще песня про алюминиевые огруцы — то явно про тебя. Сам задаешь вопросы, и сам же игнорируешь ответы. Продолжай в том же духе. Все ждешь удобного тебе ответа. Ну, так зачем тогда задавать вопросы то, если ответ тебе известен, но он тебя не удовлетворяет?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Протоколы, сообщения: что дает паттерн-матчинг?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 07.05.07 18:51
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Да, в русском языке новых букв за это внемя не появилось. Но ты не теряй надежды.


VD>Вообще песня про алюминиевые огруцы — то явно про тебя. Сам задаешь вопросы, и сам же игнорируешь ответы. Продолжай в том же духе. Все ждешь удобного тебе ответа. Ну, так зачем тогда задавать вопросы то, если ответ тебе известен, но он тебя не удовлетворяет?


Я не несу ответственности за то, что ты везде, даже если темой определено, что это не к месту, пытаешься в тысячный раз рассказать про матчинг АСТ. И ничего по интересующему меня вопросу.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[5]: Протоколы, сообщения: что дает паттерн-матчинг?
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.05.07 21:46
Оценка:
Здравствуйте, 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: Протоколы, сообщения: что дает паттерн-матчинг?
От: Mamut Швеция http://dmitriid.com
Дата: 08.05.07 07:08
Оценка:
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]).



//
// Наша структура
// 

struct SendMessage 
{
    char *text;
    char *sender;
};


//
// Принимаем сообщение типа SendMessage
//

void sendMessageEventHandler(const SendMessage &m)
{
    logger::log("Sender: %s, text: %s", m.sender, m.text);
}


Если изменится структура SendMessage, то код будет продолжать работать как есть. Если же надо будет использовать и новые поля, то код и там и там поменяется.


dmitriid.comGitHubLinkedIn
Re[2]: Протоколы, сообщения: что дает паттерн-матчинг?
От: Курилка Россия http://kirya.narod.ru/
Дата: 08.05.07 07:26
Оценка:
Здравствуйте, Mamut, Вы писали:

M>Если изменится структура SendMessage, то код будет продолжать работать как есть. Если же надо будет использовать и новые поля, то код и там и там поменяется.


Далеко не обязательно, что будет продложать работать как есть. Не зря Ulf Wiger выступает против чрезмерного использования record'ов — скрое всего получится, что при обновлении нужно перекомпилять исходники всех клиентов.
Ну невозможно обеспечить обработку изменившихся событий не меня код, который эти события обрабатывает.
Re[2]: Протоколы, сообщения: что дает паттерн-матчинг?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 08.05.07 07:36
Оценка:
Здравствуйте, 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: Протоколы, сообщения: что дает паттерн-матчинг?
От: Gajdalager Украина  
Дата: 08.05.07 09:52
Оценка: 35 (3) -1
Здравствуйте, eao197, Вы писали:

E>Интересны преимущества именно по отношению к гибридным языкам, вроде Scala, где есть "обычное" ООП (поскольку, если я правильно понимаю, в более фунциональных языках, вроде Erlang и Haskell, ситуация будет/может выглядеть иначе).


Лично мне кажется, что паттерн матчинг удобнее применять там, где у нас постоянный (или почти постоянный) набор сообщений, а вот обработчики (или, точнее, ситуации обработки) регулярно меняются и добавляються.
Чтобы было понятно, что я имею в виду, приведу пример. Пускай у нас есть 2 сообщения и 2 ситуации. Напишем для них тривиальную логику:
Java
iterface Message {
    // Это для обработчика 1
    void 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]: Протоколы, сообщения: что дает паттерн-матчинг?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 08.05.07 10:06
Оценка: -1
Здравствуйте, Gajdalager

Пара соображений, если не возражаешь.

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 нужно посмотреть на ситуацию именно с такой стороны. Спасибо за потраченное время

IIRC, в этой статье
Автор: eao197
Дата: 09.02.07
авторы Scala рассматривают разные способы использования PM и других способов матчинга объектов. И приводят соображения о том, в какой ситуации какой способ предпочтительнее. И эффективнее, что иногда важно.

G>ЗЫ А еще можно подумать о визиторе, хотя, ИМХО, перед он меркнет перед красотой ПМ


У визитора перед ПМ есть еще одно интересное преимущество -- при добавлении нового сообщения в протокол в визитор обязательно добавляется новый абстрактный метод, который обязательно должен получить конкретную реализацию. Т.е. код по обработке старой версии протокола просто не скомпилируется. В ПМ же придется в динамике проверять реацию на новые сообщения. Например, для тех же валидаторов входящих сообщений схема с абстрактными методами визиторов является еще одним уровнем защиты Такая вот извечная дилема статика/динамика. Хотя, определенно, будут находится ситуации, когда ПМ даже в этих случаях удобнее (например, если новые сообщения нужно просто игнорировать).


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[3]: Протоколы, сообщения: что дает паттерн-матчинг?
От: palm mute  
Дата: 08.05.07 10:15
Оценка: +1
Здравствуйте, eao197, Вы писали:

E>У визитора перед ПМ есть еще одно интересное преимущество -- при добавлении нового сообщения в протокол в визитор обязательно добавляется новый абстрактный метод, который обязательно должен получить конкретную реализацию. Т.е. код по обработке старой версии протокола просто не скомпилируется. В ПМ же придется в динамике проверять реацию на новые сообщения. Например, для тех же валидаторов входящих сообщений схема с абстрактными методами визиторов является еще одним уровнем защиты Такая вот извечная дилема статика/динамика. Хотя, определенно, будут находится ситуации, когда ПМ даже в этих случаях удобнее (например, если новые сообщения нужно просто игнорировать).


Компиляторы и Haskell, и Ocaml умеют предупреждать о неполном/избыточном наборе паттернов. Компилятор Скалы, предполагаю, тоже.
Re[3]: Ответ на два ответа :)
От: Mamut Швеция http://dmitriid.com
Дата: 08.05.07 10:29
Оценка: +1
E>Пока я думаю, что в примере, к которому я доколупался, просто пошли по пути наименьшего сопротивления -- демо и есть демо, чего изобретать велосипед.

Скорее всего так и есть. Просто без грамотного проектирования наломать дров можно вне зависимоти от основной парадигмы языка


dmitriid.comGitHubLinkedIn
Re[4]: Протоколы, сообщения: что дает паттерн-матчинг?
От: palm mute  
Дата: 08.05.07 11:05
Оценка:
Здравствуйте, palm mute, Вы писали:


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]: Протоколы, сообщения: что дает паттерн-матчинг?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 08.05.07 11:17
Оценка:
Здравствуйте, 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]: Протоколы, сообщения: что дает паттерн-матчинг?
От: palm mute  
Дата: 08.05.07 11:46
Оценка: 66 (3) +1
Здравствуйте, 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]: Протоколы, сообщения: что дает паттерн-матчинг?
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 08.05.07 12:06
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>В данном случае достаточно sealed abstract class Msg:

PM>http://www.nabble.com/-scala---compiler-verification-of-exhaustive-matching-t3625118.html
PM>Тогда предупреждения будут.

Ага, есть.

E>>Чесно говоря, я не вижу возможностей для Scala информировать меня о неполных case в match, т.к. новые case-классы могут быть добавлены уже после компиляции объекта Demo, в другом модуле, другим разработчиком.


PM>В случае sealed базового класса — не могут. И согласись, в случае Посетителя новые сообщения в другом модуле другим разработчиком тоже не добавляются.


Да.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[3]: Протоколы, сообщения: что дает паттерн-матчинг?
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.05.07 17:28
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Чем так плохо?


А>
А>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]: Протоколы, сообщения: что дает паттерн-матчинг?
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.05.07 17:28
Оценка:
Здравствуйте, Gajdalager, Вы писали:

G>А теперь попробуем добавить новое сообщение. В ООП варианте это проще — нужно просто заимплементить интерфейс, в обработчики изменения вносить не нужно. В ПМ-варианте же нужно вносить изменения во все матчеры.


Поясни, плиз, чем отличается добавление реализации метода от добавления case-а?

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

На мой взгляд ты ошибашся на счет простоты. Код на Яве уже значительно больше, сложнее и непонятнее. Этого уже достаточно чтобы не писать так.

Так что единственная предпосылка против паттерн-матчинга — это возможность обработки сообщений во внешних модулях при словии изменения списка сообщений. Вот тут конечно паттерн-матчинг никуда не годен. Он создает жесткую связь с конкретной версией "сервера". При изменении сервера прийдется перекомпилировать и всех клиентов. Вот только решение на Яве тут ничем не отличается.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.