Entry tags:
downcasting
Downcasting - операция, как известо, опасная. К сожалению, совсем избавиться от неё нельзя1. Например, в случае, если мы работаем с колбэками, принимающими параметры, типы которых надо понизить в самих колбэках. Или вспомним обычный метод
Но, раз нельзя избавиться, то мы можем по крайней мере постараться снизить его опасность. Для этого необходимо иметь гарантии, что объект имеет нужный нам тип:
или перехватывать исключение
В результате, мы получаем гибкий и безопасный код, проверка у которого вынесена из клиентского кода в сигнатуру. Например, реализуем контроллер, принимающий список обработчиков самых разных типов событий, и вызывающий нужный. Нам потребуется пара расширений.
и один импорт, необходимый для downcasting-а.
Сам обработчик будет принимать любое событие и возвращать некий результат. Обработчик параметризуется типом этого результата:
Сам контроллер должен найти нужный нам обработчик и снизить3 для него тип. Если же обработчик не найден, то позвать обработчик по умолчанию. Искать он будет в списке, поэтому достаточно свернуть этот список по функции, которая попытается откастить событие и в случае неудачи продолжить цепочку.
Несложно и функционально, правда? Мы можем описать конкретные контроллеры. Например, облегченную IO версию с типом IO ().
А вот и пример. У нас есть несколько событий.
И пара обработчиков - для первой пары событий.
Тогда контроллер будет выглядеть так:
Ещё один момент. В данной реализации контроллер может принимать обработчики разных типов. А что, если мы захотим иметь отдельные обработчики для левой и правой кнопки? Или для двойного щелчка и одинарного? В этом случае нам необходимо сообщать в обработчике, обработал ли он событие. Эту функциональность просто добавить, используя
(1) "нельзя" - значит, что у меня код не избавлен совсем от этого запаха.
(2) есть, конечно
(3) в данном случае лучше, наверное, говорить - "уточнить"
equals
в Java.Но, раз нельзя избавиться, то мы можем по крайней мере постараться снизить его опасность. Для этого необходимо иметь гарантии, что объект имеет нужный нам тип:
if (source instanceof Button) { Button btn = (Button) src; ... }
или перехватывать исключение
ClassCastException
. В Haskell, в одном из самых безопасных языков в мире, эти два действия - проверка и downcasting объединены, что делает операцию в целом безопасной2.cast :: (Typeable a, Typeable b) => a -> Maybe b
В результате, мы получаем гибкий и безопасный код, проверка у которого вынесена из клиентского кода в сигнатуру. Например, реализуем контроллер, принимающий список обработчиков самых разных типов событий, и вызывающий нужный. Нам потребуется пара расширений.
> {-# LANGUAGE ExistentialQuantification, DeriveDataTypeable #-}
и один импорт, необходимый для downcasting-а.
> import Data.Typeable (Typeable, cast)
Сам обработчик будет принимать любое событие и возвращать некий результат. Обработчик параметризуется типом этого результата:
> data Listener a = forall e. Typeable e => Listener (e -> a)
Сам контроллер должен найти нужный нам обработчик и снизить3 для него тип. Если же обработчик не найден, то позвать обработчик по умолчанию. Искать он будет в списке, поэтому достаточно свернуть этот список по функции, которая попытается откастить событие и в случае неудачи продолжить цепочку.
> listen atlast listeners event = foldr go (atlast event) listeners > where > go (Listener l) rest = maybe rest l (cast event)
Несложно и функционально, правда? Мы можем описать конкретные контроллеры. Например, облегченную IO версию с типом IO ().
> type ListenerIO = Listener (IO ()) > listenIO :: Typeable e => [ListenerIO] -> e -> IO () > listenIO = listen $ \e -> return ()
А вот и пример. У нас есть несколько событий.
> data KeyboardEvent = Letter Char | Enter > deriving (Typeable) > data MouseEvent = LeftBtn | RightBtn > deriving (Typeable) > data IdleEvent = IdleEvent > deriving (Typeable)
И пара обработчиков - для первой пары событий.
> -- просто, чтобы меньше писать > p = putStrLn > listenKeyboard (Letter x) = p [x] > listenKeyboard Enter = p "Enter" > listenMouse LeftBtn = p "Left" > listenMouse RightBtn = p "Right"
Тогда контроллер будет выглядеть так:
> foo :: Typeable e => e -> IO () > foo = listen (const $ p "Unknown") [Listener listenKeyboard, Listener listenMouse] *Main> foo LeftBtn Left *Main> foo (Letter 'x') x *Main> foo IdleEvent Unknown
Ещё один момент. В данной реализации контроллер может принимать обработчики разных типов. А что, если мы захотим иметь отдельные обработчики для левой и правой кнопки? Или для двойного щелчка и одинарного? В этом случае нам необходимо сообщать в обработчике, обработал ли он событие. Эту функциональность просто добавить, используя
Maybe
. И всё таки жалко, что строки из case
(паттерн -> выражение) не являются first-class в Haskell.(1) "нельзя" - значит, что у меня код не избавлен совсем от этого запаха.
(2) есть, конечно
unsafeCoerse
, но мы не будем его пользовать.(3) в данном случае лучше, наверное, говорить - "уточнить"
no subject
Вот именно, поэтому первый метод не проходит - у нас ещё нет событий, но есть обработчики для разных типов событий. Мы не можем написать [listen (Letter x), listen LeftBtn], потому что событий Letter/LeftBtn ещё нет.
Что касается второго метода, то опять же - получается, что у нас всего один listen на один тип a. Но ведь для разных условий мы можем по разному обрабатывать одно и то же событие? Классы типов - очень жёсткое решение в этом случае. Чтобы это обойти придётся заводить классы под разные задачи, а это уровень кода, значит рантайм формировать списки обработчиков мы тоже не сможем.
no subject
2. В графических программах классы под разные задачи уже есть, они называются — окно, кнопка, текстовое поле и т.д. Есть иерархия типов, начинающаяся с Widget, всяческие окна и кнопки — его подтипы, и у каждого есть методы onKeyPress, onMouseMove и так далее. Обработчик событий от мыши тогда будет всегда одинаковый — найти виджет, внутри которого (геометрически) произошло событие, и вызвать его метод onMouseMove. Что происходит, когда появляется новое событие, кажем, ScreenTouch, не предусмотренное виджетами? Во-первых, можно добавить дефолтный пустой onScreenTouch в корневой тип, и все его унаследуют, а в нужных случаях (когда действительно требуется реакция) мы его переопределим. Во-вторых, можно завести отдельную иерархию виджетов, умеющих отвечать на ScreenTouch, и отдельный список таких объектов. Если нам в каком-нибудь текстовом поле нужна нестандартная обработка какого-нибудь mouse move, мы просто делаем новый виджет "нестандартное текстовое поле".
2а. В других случаях мы вводим для класса несколько фундаментальных методов (с разными реализациями для разных инстансов, разумеется), надеясь, что из них можно скомбинировать любую желаемую операцию. Например, в классическом примере графического редактора имеется класс Shape, у которого есть методы draw, translate, rotate, scale и так далее.
no subject
Или я чего то не понимаю.
no subject
no subject
Шут с ними, с кнопками, я расскажу, как получился этот пост. VladD2 с RSDN высказался в том смысле, что Haskell не обладает компонентностью. В качестве примера он привёл WPF дизайнер. Смысл в том, цитирую "Когда можно в работающем приложении подгружать разные компоненты и получать разное поведение". В работающем! Т.е. классы кнопок дописать мы уже не сможем - это не лисп или питон какой нибудь. Одинаковые обработчики тоже отпадают, потому что речь сейчас не о них. И вот есть у нас две кнопки, к событиям (сигналам) которых мы хотим привязаться. Как это сделать? В Яве это будут две имплементации Listener. Каждая из них учитывает своё событие и может кастить параметры. А потом мы у одной из этих кнопок заменяем обработчик (listener) на другой. С классами типов всё это становится уж очень сложным.
no subject
Может быть, как-нибудь сяду и напишу на Template Haskell систему объектов, со всеми нужными прибамбасами.
no subject
Если да, то это к вопросу никаким боком не относится. Мне так кажется.
Я хочу получить библиотечную возможность подключать к потоку событий (который может включать пользовательские события - т.е. типы данных пользователя) самые разные обработчики. Обработчики сами должны определять их ли событие пришло и обрабатываеть его соответствующим образом. Хороший пример - исключения. Есть код, я хочу иметь универсальный catch, которому передаю этот код и свои обработчики, а он сам всё за меня должен порешать. Клиентский код должен быть простым - библиотечный не обязательно.
Т.е. одно из решений - я имею простые функции ConcreteEvent -> Result. Это, по-моему, удобно.
Как может выглядеть такая библиотечка? Я предложил способ и мне было интересно узнать альтернативы. Вы говорите, что они есть, но я пока не пойму, что собственно предлагается.
Сейчас я должен уйти, но ночью (или завтра) обязательно отвечу :-)
no subject
По-моему, Влад под компонентностью понимает именно это.
no subject
Dynamic Applications From the Ground Up (http://www.cse.unsw.edu.au/~dons/papers/SC05.html) приводилось на RSDN много раз.
no subject
no subject