downcasting
Feb. 5th, 2009 12:15 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
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
Date: 2009-02-05 11:00 pm (UTC)Dynamic Applications From the Ground Up (http://www.cse.unsw.edu.au/~dons/papers/SC05.html) приводилось на RSDN много раз.