Re[17]: Функциональные типы (параллельная ветка)
От: VladD2 Российская Империя www.nemerle.org
Дата: 27.06.05 22:21
Оценка: -3
Здравствуйте, eao197, Вы писали:

E>Я понял, что ты говоришь про указатели на методы класса.


Это радует.

E>Один из use case в которых я использую указатели на методы -- это реализация в объекте-классе чего-то типа конечного автомата. Наружу такой объект предоставляет, например, один метод: receive_event( event_t event ), а внутри себя должен выполнить действия в зависимости от состояния объекта. Решение в лоб выглядело бы так:


Знашь, я заметил за тобой одну проблему которая все время убивает дискуссию. Ты излишне переусложняешь свои примеры.

Какие-то конечные автоматы... Надо выделить основную сущьность и показать ее суть, а не наворачивать огромные примеры в которых сложно разобраться (а точнее очень не хочется разбираться).

Твой пример изумительно решается как на на обычных указателях на глобальны функции (не методы класса), так и на делегатах. Вот реализация на делегатах:
delegate void EventHandler(event_t event);
delegate void MyEventHandler(int eventId);

class some_class
{
    public some_class() { _eventHandler = state_one_handler; }
    
    MyEventHandler _eventHandler;

    void receive_event(int eventId)
    {
        _eventHandler(eventId);
    }

    void state_one_handler(int eventId)
    {
        if (eventId == 1)
                do_something();
        else if (eventId == 2)
                // Для переключения состояния просто меняем указатель на текущий обработчик.
                _eventHandler = state_two_handler;
    }

    void state_two_handler(int eventId)
    {
        // ...
    }

    void do_something() { }
}


Основная суть здесь использование указателя на метод в качестве ссылки на алгоритм.

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

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

Все что нужно сделать, это убрать этот лишний контроль. Но теперь уже в дело вступили амбиции. Убрать это ограничение значит признать собственную ошибку. А вот это-то похоже Страуструп делать очень не хочет. Не мог же он ошибиться в дизайне?

E>А можно было бы пойти еще дальше. Скажем завести отдельный map< event_t, (some_class_t::*)() > для каждого состояния. И занести в эти map-ы указатели на методы, которые должны вызываться в конкретном состоянии по конкретному событию. Тогда смена состояния будет означать просто смену указателя на нужный map. А работа метода receive_event -- поиск указателя в map-е по идентификатору события и вызов найденого обработчика.


Ага. Очень хороший прием. И в Шарпе он используется на право и на лево. Причем выглядит очень компактно и красиво. В сочетании с рефлекшоном он позволяет просто таки творить чудеса. Вот, например, простенький пример крэкера сообщений написанного на C# Крекер Windows-сообщений в AOP-стиле
Автор: VladD2
Дата: 11.05.04
. Благодаря мапу делегатов, рефлекшону и атрибутам обработку сообщений виндовс получилось сделать декларативным:
// Костанты-идентификаторы сообщений. Можно надыбать на www.codeproject.com или в winuser.h
const int WM_LBUTTONDOWN   =  0x0201;
const int WM_RBUTTONDOWN   =  0x0204;

// Обработчики сообщений. Атрибутом WinMsg с ними ассоциируются сами сообщения.

[WinMsg(WM_RBUTTONDOWN)]
bool OnRButtonDown(ref Message msg)
{
        BackColor = Color.Blue;
        return true;
}

[WinMsg(WM_LBUTTONDOWN)]
bool OnLButtonDown(ref Message msg)
{
        BackColor = Color.Red;
        return true;
}


А вот пример из редактора кода. Он довольно длинный, так что сначало опишу его применение, а потом приведу код.

Этот пример показывает как можно довести обработку клавиатурный ввод до полной декларативности и позволить задавать их не в программе, а в конфигурационном файле.

Итак, в контроле нужно добавить следующий код:
/// <summary>
/// Ассоциативный массив ключем котрого является клавиатурное сокращения,
/// а значением обработчик выполняемый при нажатии этого клавиатурного 
/// сокращения.
/// </summary>
private KeyboardShortcutsMap _keyboardMap;

...
// В конструкторе:
_keyboardMap = new KeyboardShortcutsMap(this);

...

    protected override void OnKeyDown(KeyEventArgs e)
    {
        if (e.KeyValue >= 16 && e.KeyValue <= 18) // Alt, Control, Shift
            return;

        // Получаем обработчик ассоциирванный с текущим клавиатурным сокращением.
        KeyboardShortcutsMap.KeyHandler keyDownHandler = _keyboardMap[e.KeyData];

        if (keyDownHandler != null)
        {
            keyDownHandler();
            e.Handled = true;
            e.SuppressKeyPress = true;
        }
        else
            base.OnKeyDown(e);
    }


А вот так выглядит конфигурационный файл:
<?xml version="1.0" encoding="utf-8" ?>
<Shortcuts>
    <!-- Навигация по тексту -->
    <Shortcut Key="Up"                      Action="CaretVirtualLineUp"/>
    <Shortcut Key="Down"                    Action="CaretVirtualLineDown"/>
    <Shortcut Key="Shift | Up"              Action="CaretVirtualLineUpExtend"/>
    <Shortcut Key="Shift | Down"            Action="CaretVirtualLineDownExtend"/>
    <Shortcut Key="Home"                    Action="CaretVirtualLineHome"/>
    <Shortcut Key="End"                     Action="CaretVirtualLineEnd"/>
    <Shortcut Key="Shift | Home"            Action="CaretVirtualLineHomeExtend"/>
    <Shortcut Key="Shift | End"             Action="CaretVirtualLineEndExtend"/>
    <Shortcut Key="Left"                    Action="CaretLeft"/>
    <Shortcut Key="Control | Left"          Action="CaretWordLeft"/>
    <Shortcut Key="Control | Right"         Action="CaretWordRight"/>
    <Shortcut Key="Shift | Control | Left"  Action="CaretWordLeftExtend"/>
    ...
    <!-- Клипборд -->
  <Shortcut Key="Control | C"             Action="Copy"/>
  <Shortcut Key="Control | Insert"        Action="Copy"/>
    <Shortcut Key="Control | V"             Action="Paste"/>
    <Shortcut Key="Shift | Insert"          Action="Paste"/>
    <Shortcut Key="Control | X"             Action="Cut"/>
    <Shortcut Key="Shift | Delete"          Action="Cut"/>
    ...
</Shortcuts>


А вот так выглядит реализация:

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
using System.Reflection;

namespace Rsdn.Editor
{
    /// <summary>
    /// Хэлпер-класс считывающий информацию о клавиатурных сокращениях из 
    /// файла настроек или ресурсов и преобразующий его во внутренний словарь.
    /// После инициализации можно пользоваться индексером объекта чтобы
    /// получать делегаты-обработчики клавиатурных событий.
    /// Файл должен называться KeyboardShortcutsMap.xml.
    /// Файл с описанием клавиатурных сокращений может находиться
    /// в каталоге %APPDATA%\Rsdn.Editor иди каталоге где располагаться сборка
    /// в котророй находится элемент управления.
    /// Если файл не найден в одном из этих каталогов, то описание 
    /// клавиатурных сокращений берется из ресурсов.
    /// </summary>
    internal class KeyboardShortcutsMap
    {
        /// <summary>
        /// Тип метода вызываемого при клавиатурном сокращении.
        /// </summary>
        public delegate void KeyHandler();

        const string MapFileName = "KeyboardShortcutsMap.xml";

        public KeyboardShortcutsMap(Control view)
        {
            using (XmlReader reader = OpenReader())
            {
                if (!reader.Read() || reader.Name != "xml")
                    throw new ApplicationException("Неверный формат файла KeyboardShortcutsMap.xml");

                while (reader.Read() && string.IsNullOrEmpty(reader.Name))
                    ;

                if (reader.Name != "Shortcuts")
                    throw new ApplicationException("Неверный формат файла KeyboardShortcutsMap.xml");

                Type keyHandlerType = typeof(KeyHandler);
                object[] args = new object[0];
                StringBuilder errors = new StringBuilder(); // сообщения об ошибках

                while (reader.Read())
                {
                    if (reader.Name == "Shortcut")
                    {
                        string key = null;
                        string action = null;

                        try
                        {
                            key = reader.GetAttribute("Key"); // читаем ключь
                            // читаем имя делегата
                            action = reader.GetAttribute("Action");

                            // Получам клавиатурное сокращение плюс делегат и инициализируем
                            // этими значениями внутренний словарь.
                            SetKeyboardHandler(ParseKeys(key),
                                (KeyHandler)KeyHandler.CreateDelegate(
                                keyHandlerType, view, action, false, true));
                        }
                        catch (Exception ex)
                        {
                            errors.AppendLine("Can't add key '" + key + "' with action '"
                                + action + "'. " + ex.Message);
                        }
                    }
                }

                if (errors.Length > 0)
                    MessageBox.Show(view.FindForm(), 
                        "При считывании файла с клавиатурными сокращениями были обнаружены "
                        + "следующие ошибки: " + Environment.NewLine + errors, Utils.AppName,
                        MessageBoxButtons.OK, MessageBoxIcon.Warning); 
            }
        }

        /// <summary>
        /// Открывает XML-файл содержащий описание клавиатурных сокращений
        /// на чтение.
        /// </summary>
        private static XmlReader OpenReader()
        {
            XmlReader reader;

            string path = Environment.GetFolderPath(
                Environment.SpecialFolder.ApplicationData);

            path = Path.Combine(Path.Combine(path, Utils.AppName), MapFileName);

            // Если файл с клавиатурными настройками существует в папке "Application Data" 
            // (%APPDATA%), или в папке где расположена DLL-а, то читаем настройки из него.
            // Иначе чиатаем настройки из файла сохраненного при компиляции в ресурсах.
            if (File.Exists(path))
                reader = XmlReader.Create(path);
            else if (File.Exists(path = Path.Combine(Utils.GetModulePath(), MapFileName)))
                reader = XmlReader.Create(path);
            else
                reader = XmlReader.Create(new StringReader(
                    Properties.Resources.KeyboardShortcutsMap));
            return reader;
        }

        /// <summary>
        /// Преобразует строку содержащую имена клавишь в Keys.
        /// Имена клавишь могут объеденяться по или (знаком "|").
        /// </summary>
        /// <example>"Shift | Control | Right"</example>
        /// <param name="key">Клавиатурное сокращение в виде строки.</param>
        private static Keys ParseKeys(string key)
        {
            Keys keys;
            // Разбиваем ключь на отдельные значения
            string[] keyStrs = key.Split('|');
            keys = 0;

            foreach (string value in keyStrs)
                keys |= (Keys)Enum.Parse(typeof(Keys), value);
            return keys;
        }

        /// <summary>
        /// Возвращает делегат-обработчик события соотвествующий клавиатурному
        /// сокращению или null.
        /// </summary>
        /// <param name="shortcut">Клавиатурное сокращение.</param>
        /// <returns>Делегат обработчки.</returns>
        public KeyHandler this[Keys shortcut]
        {
            get
            {
                KeyHandler keyHandler;

                if (_kbdMap.TryGetValue(shortcut, out keyHandler))
                    return keyHandler;

                return null;
            }
        }

        /// <summary>
        /// Вспомогательный метод позволяющий пуростить инициализацию _kbdMap.
        /// Ассоциирует клавиатурное сокращение и его обработчик.
        /// </summary>
        /// <param name="keyData">Клавиатурное сокращение.</param>
        /// <param name="keyHandler">Обработчик.</param>
        private void SetKeyboardHandler(Keys keyData, KeyHandler keyHandler)
        {
            if (_kbdMap.ContainsKey(keyData))
                Trace.WriteLine("The keyboard shortcut '" + keyData + "' be found twice.");

            _kbdMap[keyData] = keyHandler;
        }

        /// <summary>
        /// Ассоциативный массив ключем котрого является клавиатурное сокращения,
        /// а значением обработчик выполняемый при нажатии этого клавиатурного 
        /// сокращения.
        /// </summary>
        private Dictionary<Keys, KeyHandler> _kbdMap = 
            new Dictionary<Keys, KeyHandler>();
    }
}



Теперь зададимся впоросом, в чем главное отличие этих примеров от твоего (если не смотреть на применение рефлекшона и других компонентных технологий)? Так вот главное отличие, что класс с реализацией фичи, и класс предоставляющий список функций — это два разных класса! Вот этого и не позволяют сделать указатели на методы класса в С++. А именно это применение самое желанное.

Так что указатели на методы класса в С++ — это всего лишь досадное недоразумение. Создание частного случая вместо реализации общего.

Ну, а все эти сигналы, бинды, фаншоны из С++ не боллее чем попытка обойти достадные ограничения за счет возможностей метапрограммирования и нехилых изысков.

E><...Эмоциональное описание того, что тебе кажется неправильным поскипано...>


E>Твоя точка зрения понятна. Но, имхо, она не правильная, а я согласен со Страуструпом.


Жаль, что не смог убедить. Но спасибо хотя бы за то, что я не услышал от тебя слов вроде тех что рядом излил ПК.


VD>>Какое отношение этот рассказ имеет к вопросу? Повторю еще раз цитаты:

VD>>>>>Да, не понимаю. Сборка это набор байт. Всегда можно скачать...

E>>>>Нет. Далеко не всегда.


VD>>>И когда нельзя?

VD>>>Раскрывай.

E>Влад, вот ты часто говоришь, что занимаешься интеропом. Но ты представляешь себе реальные прикладные протоколы, которые используются для организации взаимодействия нескольких удаленных машин? Вот скажем обмениваются информацией о транзакциях два банковких сервера. Или несколько машин в кластере обмениваются результатами вычислений. Или даже когда браузер обращается к web-серверу за страничкой. Все они ожидают получить данные, именно данные, а не код, который эти данные может распарсить. И проблемы начинаются, когда версия данных на одной стороне начинает разсогласовываться с тем, что ожидает другая сторона.


Ясно. Прямого ответа на вопрос я не получу. Ну, да понятно почему...

VD>>Что же до сериализации, то это проблемы С++ в ремоутинге и КОМ-е они решаются.


E>Насколько я знаю, там все решается на уровне жестко определенных интерфейсов. Причем не только в КОМ-е, но и в Corba. Как только интерфейсы перестают удовлетворять потребностям приходится вводить новые интерфейсы. Затем новые. Затем опять новые и т.д.


И да, и нет. Более вреный ответ будет звучать так: там все решается наличием исчерпывающих метаданных в рантайме. Если есть метаинформация, то проблемы вроде маршалинга и сериализации разрешимы. Проблема С++ в том что его авторы вообще не задумывались о метаинформации в программе.

E>А вот в Asn1 было введено понятие "extension points" -- это такие места в спецификации протокола, где говорится -- вот в этом месте в будущем может что-то появиться.


Очень смахивает на введение лишних параметров на всякий случай. Мне кажется это очень не верное решение. Лучше не пытаться угадать что будет в будущем, а просто позволить читать информацию о том, что есть в настоящем.

E>Вот я и спрашивал, поддерживает ли BinaryFormatter в .Net подобные фишки. И все мои примеры как раз были об этом.


У тебя есть некоторое представление о том, ка должен быть реализован BinaryFormatter если для его реализации использовать С++ и привычные тебе идиомы/паттерны. Но это не вереный подход. Для понимания того что такое BinaryFormatter и как он устроен нужно понять идиомы и паттерны дотнета и отказаться от взгляда на мир с точки зрения С++.

В общем, проще говорить о возможностях. Сформулируй какие возможности ты хотел бы видеть от BinaryFormatter и я тебе скажу реализованы ли они или нет.

E>Понятно. Только, если тебе интересно, то выбери время и сходи. А нет, так я не вижу смысла копипастить сюда несколько полтора десятка килобайт описаний.


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

E>Потому что конкретный прикладной протокол придется заморачивать не только на передачу прикладных данных, но и на передачу кода.


Это можно сделать введя очень простенький компонент. В его задачи будет входить контроль наличия сборки (что в дотнете делается простым подключением на специальное событие) и подкачку нужной сборки если сборки еще нет.

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

E>Кроме того, если речь идет о банковских системах, то на 100% могу утверждать, что пробить через безопасность банка решение о динамической подгрузке на банковский сервер извне какого-то кода будет архисложно.


Это если люди отвечающие за решение совсем дремучи. Все проблемы безопасности решаются. Копировать можно только публичные интерфейсы.

E>Вот именно. Но я говорю о случаях, когда сериализация/десериализация на разных сторонах делается по разным версиям описания схемы данных.


Звучит не разумно.

VD>> Ну, и всегда можно сделать простенькое решение которое сможет копировать нужные интерфесные сборки в автомате. Итго — это не языковая проблема. Это проблема дизайна.


E>Именно дизайна. И попытаться решить нетривиальную проблему дизайна простенькими средствами языка может быть слишком сложно.


Ты погряз в догмах и проблемах С++. То что ты называшь "нетривиальной проблемой дизайна" на самом деле является обычной пробемой дизайна довольно не сложно решаемой в компонентных средах.
... << RSDN@Home 1.1.4 beta 7 rev. 466>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.