HeinrichApfelmus / threepenny-gui

GUI framework that uses the web browser as a display.
https://heinrichapfelmus.github.io/threepenny-gui/
Other
441 stars 77 forks source link

Sample using custom monad stack #161

Closed jerbaroo closed 7 years ago

jerbaroo commented 7 years ago

People will perhaps want to use some state or read-only config, or other functionality and hence want to use StateT, ReaderT, or some other monad transformers.

An attempt using ReaderT is shown below, however the use of lift is not very nice. Perhaps I'm approaching it wrong.

A sample showing how to use a custom monad stack could be quite helpful.

Based on feedback I could send in a PR.

module Lib where

import           Control.Monad.Reader
import qualified Graphics.UI.Threepenny      as UI
import           Graphics.UI.Threepenny.Core hiding (Config)

-- |Config the app requires.
data Config = Config { cButtonText :: String }

-- |Monad the app runs in.
type Foo a = ReaderT Config UI a

-- |Run the Foo monad.
runFoo :: Foo a -> Config -> UI a
runFoo = runReaderT

-- |Start the app.
start :: Int -> IO ()
start port =
    startGUI defaultConfig { jsPort = Just port } $
        \w -> runFoo (app w) "Some Config"

-- |Our app in the Foo monad.
app :: Window -> Foo ()
app window = do
    buttonText <- cButtonText <$> ask
    button <- lift $ UI.button # set UI.text buttonText
    void $ lift $ getBody window #+ [element button]
HeinrichApfelmus commented 7 years ago

Seems right to me, I don't think that there is anything wrong with the way you wrote it.

That said, using a ReaderT monad or other monad stack on top of UI is probably less useful than one might think. The problem is that any event handler, which you may attach to, say, a button, still needs to be in the UI monad. It's easier to use a parameter or a mutable variable in this case.