downcasting

Feb. 5th, 2009 12:15 pm
lomeo: (лямбда)
[personal profile] lomeo
Downcasting - операция, как известо, опасная. К сожалению, совсем избавиться от неё нельзя1. Например, в случае, если мы работаем с колбэками, принимающими параметры, типы которых надо понизить в самих колбэках. Или вспомним обычный метод 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) в данном случае лучше, наверное, говорить - "уточнить"

Date: 2009-02-05 11:00 pm (UTC)
From: [identity profile] lomeo.livejournal.com
Ну, тогда вообще проблем не вижу:
Dynamic Applications From the Ground Up (http://www.cse.unsw.edu.au/~dons/papers/SC05.html) приводилось на RSDN много раз.

Profile

lomeo: (Default)
Dmitry Antonyuk

April 2024

S M T W T F S
 123456
7891011 1213
14151617181920
21222324252627
282930    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 24th, 2025 03:21 pm
Powered by Dreamwidth Studios