vir (asviraspossible) wrote,

Xpost to Haskell-Cafe

Я писал GUI-проложение на Gtk2hs. Это интерпретатор ламбда-исчисления и комбинаторной логики, если кому надо могу поделиться, лицензия GPL.

Проблема в том, что GUI-код стал ужасно уродлив и я горю желанием полностью его переписать. Я смотрел в сторону всяких там FRP, но никто так мне и не объяснил, что такое FRP. Посты Conal Eliot'а слишком расплывчаты, чтобы служить определением. Хочется попробовать Grapefruit но я теряюсь, когда вижу стрелочную нотацию.

Я думаю над более легковесным и более императивным решением. Оно ближе к CSP (Communicating Sequential Processes) чем к FRP. Я тут набрасал на коленке программку, которая бы проиллюстрировала мой подход.

&lquo;Поведение&rquo; в моей версии это монада и не просто монада, а IO-монада (MonadIO), т. е. можно использовать любой ввод-вывод (а значит и Gtk2hs методы), какой захочется. Разница в том, что вы не навешиваете статичные обработчики событий, в которых пытаетесь понять что делать, глядя на глобальное состояние программы, вместо этого вы добавляете и удаляете обработчики, так часто, как это необходимо. &lquo;Поведения&rquo;, в таком случае выглядят, как процессы, которые могут заснуть в ожидании события от пользовательского интерфейса, и проснуться когда получат событие.

Есть несколько деталей, которые ещё нужно уточнить:

  • Как ждать несколько разных событий одновременно
  • Как обрабатывать IO-исключения

В общем-то сам код:

{-# LANGUAGE ExistentialQuantification #-}
module Main where

import Data.IORef
import System.Glib
import Graphics.UI.Gtk
import Control.Monad.Trans

type Event obj = IO () -> IO (ConnectId obj)

data Behaviour a =
  forall b. BBind (Behaviour b) (b -> Behaviour a)
  | BIO (IO a)
  | forall obj. GObjectClass obj => BWaitEvent (Event obj) (Behaviour a)

instance Monad Behaviour
  where action >>= generator = BBind action generator
        return a = BIO (return a)

instance MonadIO Behaviour
  where liftIO action = BIO action

runBehaviour :: Behaviour a -> IO a
runBehaviour (BBind (BWaitEvent event after) f) = runBehaviour (BWaitEvent event (after >>= f))
runBehaviour (BBind (BIO a) f) = a >>= \x -> runBehaviour (f x)
runBehaviour (BBind (BBind a f) g) = runBehaviour (a >>= (\x -> f x >>= g))
runBehaviour (BIO a) = a
runBehaviour (BWaitEvent event after) =
  do sigIdRef <- newIORef (error "You can't access sigIdRef before signal is connected")
     sigId <- event $
       do sigId <- readIORef sigIdRef
          signalDisconnect sigId
          runBehaviour after
          return ()
     writeIORef sigIdRef sigId
     return (error "You can't expect result from behaviour")

waitEvent :: GObjectClass obj => Event obj -> Behaviour ()
waitEvent event = BWaitEvent event (return ())

main :: IO ()
main =
  do initGUI
     window <- windowNew
     onDestroy window mainQuit
     set window [windowTitle := "Hello World"]
     button <- buttonNew
     let buttonB label =
           do liftIO $ set button [buttonLabel := label]
              waitEvent (onClicked button)
              buttonB (label ++ "*")
     runBehaviour (buttonB "*")
     set window [containerChild := button]
     widgetShowAll window
     mainGUI

Здесь buttonB label — это поведение. Оно выставляет надпись на кнопке, ждёт события нажатия кнопки, а затем работает так же, только к надписи на кнопке добавляется звёздочка. Технически, при каждом нажатии на кнопку перевешивается обработчик нажатия на тот, который выставляет надпись на кнопке на ту же, только с дополнительной звёздочкой. Подобным образом я думаю реализовывать более сложные схемы процессов. При этом в коде не нужно проверять состояния, так как будет висеть тот обработчик для которого уже статически ясно что нужно делать.

XPost to ru_declaretive

Comments for this post were disabled by the author