Using FRP and IORef in threepenny-gui - haskell

I have a little example using IORef in threepenny-gui (TPG):
testIORef :: IORef String -> Window -> UI ()
testIORef ref window = void $ do
return window # set title "Test IORef"
inCell <- UI.input
outCell <- UI.input
-- When value changes write to IORef
on UI.valueChange inCell $ \_ -> do
inValue <- get value inCell
liftIO $ writeIORef ref inValue
-- function that reads the IORef and sets the value of an element with it
let setValue oc = do
newVal <- liftIO $ readIORef ref
element oc # set value newVal
-- When enter is pressed update the value of the output
on UI.keydown inCell $ \c -> if (c==13) then setValue outCell else return outCell
getBody window #+ [ column [ grid [[string "In cell :", element inCell]
,[string "Out cell:", element outCell]
]
, string "Cells should update while typing."
]
]
I am trying to change this to use the Reactive stuff in TPG. I have got as far as making the Behaviors from the events valueChange and keyDown:
inValue <- stepper "0" $ UI.valueChange inCell
inEnter <- stepper "0" $ fmap show $ filterE (\kc-> kc==13) $ UI.keydown inCell
But I am stuck on how to use these Behaviors to save/get the value to/from the IORef. The problem is that the IORef calls will be in the UI monad, so if I use them the Behaviour will be Behavior (UI a), but then they won't work with sink. I know in this case I needn't use an IORef (eg. the currency conversion example) - but in my actual case I do.
EDIT:
I tried writing my own attribute:
valueUI :: ReadWriteAttr Element (UI String) String
so that I can set an attribute with a Behavior (UI String):
inEnter <- stepper "0" $ fmap show $ filterE (\kc-> kc==13) $ UI.keydown inCell
let getValue = fmap (const $ liftIO $ readIORef ref) inEnter
element outCell # sink valueUI getValue
The code compiles but doesn't work.

Related

How to use Atomics Counter for counting operation order of randomly occuring operations on different threads?

What I'd like to do is something like this where every time one of these print actions occurs it updates the counter to ensure that the next subsequent occurrence of a print action will always have the correct order in which it occurred among any of the possible print actions that may occur across multiple threads shown by the counter. The problem in my example is that if the IORef is read at the same time between threads then two or more print actions will have the same counter value. From what I've read it seems that using the Data.Atomics.Counter library would solve this problem but i am having a really hard time understanding how to use it do so. Can anyone show me an example or try to explain it to me please?
main = do
myref <- newIORef 1 :: IO (IORef Int)
void(forkIO (forever $ do ref <- readIORef myref
print ("hi " ++ show (ref))
modifyIORef myref (+1) ))
void(forkIO (forever $ do ref <- readIORef myref
print ("hey " ++ show (ref))
modifyIORef myref (+1) ))
forever $ do ref <- readIORef myref
print ("hello " ++ show (ref))
modifyIORef myref (+1)
I would use an MVar for this.
inc mvar = forever $ do
v <- takeMVar mvar
print v
putMVar mvar (v+1)
main = do
mvar <- newMVar 1
forkIO (inc mvar)
forkIO (inc mvar)
inc mvar
It is important that the print occur between takeMVar and putMVar, while the MVar is empty; otherwise another thread may empty the MVar and execute its print.
You could use atomicModifyIORef'. It would look something like:
increment ref = forever do
val <- atomicModifyIORef' ref \old -> (old + 1, old)
print val
main = do
ref <- newIORef 0
forkIO $ increment ref
forkIO $ increment ref
increment ref

Why is this Haskell program actually concurrent?

I am learning about simple uses of forkIO and Mvar from Marlow's book: Parallel and Concurrent Programming in Haskell. I the section MVar as a Container for Shared State the following code is exposed:
-- Creates a new Map within an MVar
new :: IO PhoneBookState
new = do
m <- newMVar Map.empty
return (PhoneBookState m)
-- Inserts a Key, Value in the Map, locking the MVar
insert :: PhoneBookState -> Name -> PhoneNumber -> IO ()
insert (PhoneBookState m) name number = do
book <- takeMVar m
putMVar m (Map.insert name number book)
-- Retrieves the Map, locking the MVar briefly
lookup :: PhoneBookState -> Name -> IO (Maybe PhoneNumber)
lookup (PhoneBookState m) name = do
book <- takeMVar m
putMVar m book
return (Map.lookup name book)
Where PhoneBookState holds an MVar (Map Name PhoneNumber). My problem comes in the main function
main = do
s <- new
sequence_ [ insert s ("name" ++ show n) (show n) | n <- [1..10000] ]
lookup s "name999" >>= print
lookup s "unknown" >>= print
The idea is both lookups happening concurrently, but in such a case, shouldn't it be something like
main = do
s <- new
sequence_ [ insert s ("name" ++ show n) (show n) | n <- [1..10000] ]
forkIO(lookup s "name999" >>= print)
lookup s "unknown" >>= print
What am I missing?

Using IORef in threepenny gui

I'm trying to set an IORef in threepenny-gui but I can't get it to work. In my app the IORef itself will be more complicated and not itself be displayed - but this example demonstrates the problem I think.
Here is my try:
testIORef2 :: IORef String -> Window -> UI ()
testIORef2 ref window = void $ do
return window # set title "Test IORef"
inCell <- UI.input
outCell <- UI.input
getBody window #+ [
column [
grid [[string " In cell::", element inCell]
,[string "Out cell::" , element outCell ]]
, string "Cells should update while typing."
]]
-- When value changes write to IORef
on UI.valueChange inCell $ \_ -> do
inValue <- get value inCell
liftIO $ writeIORef ref inValue
-- Read the IORef
refVal <- liftIO $ readIORef ref
-- Behaviour which holds the string value in the input cell
inValue <- stepper "0" $ UI.valueChange inCell
-- Behaviour which holds the value in the ref
let outValue = (const refVal) <$> inValue
-- Set the value of the output cell to the outValue
element outCell # sink value outValue
The code sort of works but the outValue is not quite up to date.
How do I fix it so that the updates are on time. Also, any improvements to the code would be welcome.
Thanks.
The code you wrote is probably not what you intended to do. The line
let outValue = (const refVal) <$> inValue
specifies that outValue is a Behavior whose value is constant and equal to refValue. In turn, the latter value is obtained from
refVal <- liftIO $ readIORef ref
which means that its the value stored by IORef at this point in time in the UI monad.
When using IORef, you want to read the value of the reference when something changes, and use this value to modify the UI content, for instance like this:
on UI.valueChange inCell $ \_ -> do
inValue <- get value inCell
liftIO $ writeIORef ref inValue
outValue <- liftIO $ readIORef ref
element outCell # set value outValue
For reasons of consistency (order of operations), it is not advisable to use an IORef as a source for a Behavior — it's either the latter or the former.
I'm not an expert on threepenny-gui, but here's my guess.
The code you write outside the on event handler is executed just once. You therefore want to include the outValue update inside said handler, e.g.
-- When value changes write to IORef
on UI.valueChange inCell $ \_ -> do
inValue <- get value inCell
liftIO $ writeIORef ref inValue
... -- compute outValue
element outCell # sink value outValue

Strange behavior of accumE with Event (UI a -> UI a)

I'm experimenting with threepenny-gui, trying to learn the FRP interface. I want to avoid all explicit shared state using accumE/accumB, instead of IORef:s. I have four different signals (start, stop, timer, reset) which all affect a global state and the user interface. I'm using accumE w $ concatenate <$> unions [e0, e1, e2, e3] to make the events share the same state w. Here is a short snippet which captures the essence of it (with only one signal):
data World = World { intW :: Int , runW :: UI () }
main :: IO ()
main = startGUI defaultConfig setup
setup :: Window -> UI ()
setup _ = do
(e0, fire) <- liftIO UI.newEvent
let e0' = action <$ e0
e <- accumE (World 0 (return ())) e0'
onEvent e $ \w -> void $ runW w
replicateM_ 5 . liftIO $ fire ()
where
action :: World -> World
action w = w { intW = succ $ intW w
, runW = liftIO . print $ intW w }
This seems to work fine (although I wonder if it is sane). However, if I instead change the event to have type Event (UI World -> UI World) (and remove the runW field), things go haywire:
data World = World { intW :: Int } deriving (Show)
main :: IO ()
main = startGUI defaultConfig setup
setup :: Window -> UI ()
setup _ = do
(e0, fire) <- liftIO UI.newEvent
let e0' = action <$ e0
e <- accumE (return (World 0)) e0'
onEvent e void
replicateM_ 5 . liftIO $ fire ()
where
action :: UI World -> UI World
action world = do
w <- world
let w' = w { intW = succ $ intW w }
liftIO $ print w'
return w'
It seems that all UI actions somehow get accumulated, and executed an increasing number of times with each event! I thought accumE was like a fold, where the accumulating state is replaced unless explicitly accumulated. What is the proper/best way of dealing with this problem in general?

Reactive table with reactive banana and gtk2hs

I have written a small application which tracks my progress in TV Series. The application is written in Haskell with functional reactive programming (FRP) with reactive banana.
The application can:
add/remove new TV Series to the table
change the season and episode of an series
I have problems writing the code that adds a new TV series to the table and wires the new events. The CRUD example from here didn't quite help me because I have more requirements then just selecting an element from the list.
How do I write a reactiveTable function like the reactiveListDisplay function from the CRUD Example in a FRP way? How can events be added for the remove button and the season and episode spin buttons after the network has been compiled?
data Series = Series { name :: String
, season :: Int
, episode :: Int
}
insertIntoTable :: TableClass t => t -> SeriesChangeHandler -> SeriesRemoveHandler -> Series -> IO ()
insertIntoTable table changeHandler removeHandler (Series name s e) = do
(rows, cols) <- tableGetSize table
tableResize table (rows+1) cols
nameLabel <- labelNew $ Just name
adjustmentS <- adjustmentNew (fromIntegral s) 1 1000 1 0 0
adjustmentE <- adjustmentNew (fromIntegral e) 1 1000 1 0 0
seasonButton <- spinButtonNew adjustmentS 1.0 0
episodeButton <- spinButtonNew adjustmentE 1.0 0
removeButton <- buttonNewWithLabel "remove"
let getSeries = do
s <- spinButtonGetValue seasonButton
e <- spinButtonGetValue episodeButton
return $ Series name (round s) (round e)
handleSeries onEvent widget handler = do
onEvent widget $ do
series <- getSeries
handler series
handleSeries onValueSpinned seasonButton changeHandler
handleSeries onValueSpinned episodeButton changeHandler
onPressed removeButton $ do
series <- getSeries
containerRemove table nameLabel
containerRemove table seasonButton
containerRemove table episodeButton
containerRemove table removeButton
removeHandler series
let tadd widget x = tableAdd table widget x (rows - 1)
tadd nameLabel 0
tadd seasonButton 1
tadd episodeButton 2
tadd removeButton 3
widgetShowAll table
main :: IO ()
main = do
initGUI
window <- windowNew
scroll <- scrolledWindowNew Nothing Nothing
table <- tableNew 1 5 True
addButton <- buttonNewWithLabel "add series"
vbox <- vBoxNew False 10
containerAdd window vbox
boxPackStart vbox addButton PackNatural 0
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
addEvent <- eventButton addButton
(changeHandler,fireChange) <- liftIO $ newAddHandler
changeEvent <- fromAddHandler changeHandler
(removeHandler,fireRemove) <- liftIO $ newAddHandler
removeEvent <- fromAddHandler removeHandler
let insertIntoTable' = insertIntoTable table fireChange fireRemove
addSeries e = do
s <- addSeriesDialog
liftIO $ insertIntoTable' s
liftIO $ mapM_ insertIntoTable' initSeries
reactimate $ addSeries <$> addEvent
reactimate $ updateSeries conn <$> changeEvent
reactimate $ removeSeries conn <$> removeEvent
network <- compile networkDescription
actuate network
onDestroy window $ do
D.disconnect conn
mainQuit
widgetShowAll window
mainGUI
I want to refactor the insertIntoTable method to use events and behaviors rather than using simple callbacks.
EDIT:
I have tried the gtk TreeView with a ListStore backend. In this scenario you don't need dynamic event switching. I have written the reactiveList function below to get a list behavior out of insert, change and remove events. It works ^^
reactiveList :: (Frameworks t)
=> ListStore a
-> Event t (Int,a) -- insert event
-> Event t (Int,a) -- change event
-> Event t (Int,a) -- remove event
-> Moment t (Behavior t [a])
reactiveList store insertE changeE removeE = do
(listHandler,fireList) <- liftIO $ newAddHandler
let onChange f (i,a) = do
f i a
list <- listStoreToList store
fireList list
reactimate $ onChange (listStoreInsert store) <$> insertE
reactimate $ onChange (listStoreSetValue store) <$> changeE
reactimate $ onChange (const . listStoreRemove store) <$> removeE
initList <- liftIO $ listStoreToList store
fromChanges initList listHandler
main :: IO ()
main = do
initGUI
window <- windowNew
addButton <- buttonNewWithLabel "add series"
vbox <- vBoxNew False 10
seriesList <- listStoreNew (initSeries :: [Series])
listView <- treeViewNewWithModel seriesList
treeViewSetHeadersVisible listView True
let newCol title newRenderer f = do
col <- treeViewColumnNew
treeViewColumnSetTitle col title
renderer <- newRenderer
cellLayoutPackStart col renderer False
cellLayoutSetAttributes col renderer seriesList f
treeViewAppendColumn listView col
return renderer
newCol "Image" cellRendererPixbufNew $ \s -> [cellPixbuf :=> newPixbuf s]
newCol "Name" cellRendererTextNew $ \s -> [cellText := name s]
seasonSpin <- newCol "Season" cellRendererSpinNew $ \s ->
[ cellRendererSpinAdjustment :=> adjustmentNew (fromIntegral (season s)) 1 1000 1 0 0
, cellText := (show $ season s)
, cellTextEditable := True
]
episodeSpin <- newCol "Episode" cellRendererSpinNew $ \s ->
[ cellRendererSpinAdjustment :=> adjustmentNew (fromIntegral (episode s)) 1 1000 1 0 0
, cellText := (show $ episode s)
, cellTextEditable := True
]
containerAdd window vbox
boxPackStart vbox listView PackGrow 0
boxPackStart vbox addButton PackNatural 0
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
(addHandler,fireAdd) <- liftIO $ newAddHandler
maybeSeriesE <- fromAddHandler addHandler
(removeHandler,fireRemove) <- liftIO $ newAddHandler
removeE <- fromAddHandler removeHandler
-- when the add button was pressed,
-- open a dialog and return maybe a new series
askSeriesE <- eventButton addButton
reactimate $ (const $ fireAdd =<< askSeries) <$> askSeriesE
-- ommit all nothing series
let insertE = filterJust maybeSeriesE
insert0E = ((,) 0) <$> insertE
seasonSpinE <- eventSpin seasonSpin seriesList
episodeSpinE <- eventSpin episodeSpin seriesList
let changeSeason (i,d,s) = (i,s {season = round d})
changeEpisode (i,d,s) = (i,s {episode = round d})
let changeE = (changeSeason <$> seasonSpinE) `union` (changeEpisode <$> episodeSpinE)
listB <- reactiveList seriesList insert0E changeE removeE
listE <- (changes listB)
reactimate $ (putStrLn . unlines . map show) <$> listE
reactimate $ insertSeries conn <$> insertE
reactimate $ updateSeries conn . snd <$> changeE
reactimate $ removeSeries conn . snd <$> removeE
return ()
network <- compile networkDescription
actuate network
onDestroy window $ do
D.disconnect conn
mainQuit
widgetShowAll window
mainGUI
I'm open for comments and suggestions.
It sounds like your problem is much closer to the Bar Tab example than the CRUD one.
The basic idea for adding new widgets--along with new behaviors and events--is to use so-called "dynamic event switching". Essentially, this is a way to put newly created events and behaviors back into your network.
The action to create a new widget has two parts. The first part is to just create the widget, using liftIO. The second is to get its inputs and use trimE or trimB as appropriate. Leaving out most of the GTk-specific details (I don't know how to use GTk :P), it'll look something like this:
let newSeries name = do
label <- liftIO . labelNew $ Just name
liftIO $ tadd labelNew 0
{- ... the rest of your controls here ... -}
seasonNumber <- trimB $ getSpinButtonBehavior seasonButton
{- ... wrap the rest of the inputs using trimB and trimE ... -}
return (label, seasonNumber, ...)
So this function creates a new widget, "trims" its inputs and returns the values to you. Now you have to actually use these values:
newSeasons <- execute (FrameworkMoment newSeries <$> nameEvents)
here nameEvents should be an Event String containing an event with the name of the new series each time you want to add it.
Now that you have a stream of all of the new seasons, you can combine it all into a single behavior of a list of entries using something like stepper.
For more details--including things like getting the aggregate information out of all of your widgets--look at the actual example code.

Resources