Is it possible to clean some code using gtk2hs? - haskell

I'm starting a GUI with haskell and gtk2hs.
I've got a notebook widget and I want to switch pages with "F1, F2 ... F11" keys.
My working code is:
import Control.Monad.Trans (liftIO)
import Graphics.UI.Gtk
main = do
initGUI
builder <- builderNew
builderAddFromFile builder "M62.glade"
window <- builderGetObject builder castToWindow "window1"
notebook <- builderGetObject builder castToNotebook "notebook1"
window `on` keyPressEvent $ tryEvent $ do "F1" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 0
window `on` keyPressEvent $ tryEvent $ do "F2" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 1
window `on` keyPressEvent $ tryEvent $ do "F3" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 2
window `on` keyPressEvent $ tryEvent $ do "F4" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 3
window `on` keyPressEvent $ tryEvent $ do "F5" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 4
window `on` keyPressEvent $ tryEvent $ do "F6" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 5
window `on` keyPressEvent $ tryEvent $ do "F7" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 6
window `on` keyPressEvent $ tryEvent $ do "F8" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 7
window `on` keyPressEvent $ tryEvent $ do "F9" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 8
window `on` keyPressEvent $ tryEvent $ do "F10" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 9
window `on` keyPressEvent $ tryEvent $ do "F11" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 10
onDestroy window mainQuit
widgetShowAll window
mainGUI
Is there betters and/or concises ways to do it?
I've tried to handle it out of the 'main' but then only "F1" works.
I don't see how to manage this boilerplate.

I don't know much about gtk2hs, but using forM_ to loop over the key indices should go a long way. Also, it seems events are a MonadPlus, so pattern match failure can be favorably replaced with guard.
forM_ [0..10] $ \i -> do
let key = "F" ++ show (i + 1)
window `on` keyPressEvent $ tryEvent $ do
pressed <- eventKeyName
guard (pressed == key)
liftIO $ notebookSetCurrentPage notebook i

How about this:
window `on` keyPressEvent $ tryEvent $ do
'F':n_ <- eventKeyName
let (n, ""):_ = reads n_
liftIO . notebookSetCurrentPage notebook $ n - 1
This is hopelessly partial: there are two partial pattern matches that can throw an exception. But that's okay, because that's what tryEvent is for. At time of writing, all other answers involve registering many event handlers, whereas this one registers only one. This should have a (slight) performance advantage.

Try splitting the repeated part into a function, like this:
import Control.Monad
import Graphics.UI.Gtk
main = do
initGUI
builder <- builderNew
builderAddFromFile builder "M62.glade"
window <- builderGetObject builder castToWindow "window1"
notebook <- builderGetObject builder castToNotebook "notebook1"
-- Split the repeated code into a reusable function, like this
let registerKeyPressEvent n =
window `on` keyPressEvent $ tryEvent $ do
pressed <- eventKeyName
guard (pressed == ("F" ++ show (n + 1)))
liftIO $ notebookSetCurrentPage notebook n
-- Thanks to Tarmil for catching a bug in the code that used to be above.
-- Tarmil got it right, so I'm borrowing his/her version.
-- Then you can call it more than once
registerKeyPressEvent 0
registerKeyPressEvent 1
registerKeyPressEvent 2
registerKeyPressEvent 3
registerKeyPressEvent 4
registerKeyPressEvent 5
registerKeyPressEvent 6
registerKeyPressEvent 7
registerKeyPressEvent 8
registerKeyPressEvent 9
registerKeyPressEvent 10
-- But even that is too verbose.
-- You can shorten it even further like this:
mapM_ registerKeyPressEvent [0..10]
mapM is like map, except for monads. The type of map is:
map :: (a -> b) -> [a] -> [b]
meaning that it takes a function and applies it to every element of a list, returning the result. The type of mapM is:
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
meaning that it takes a monadic function (such as registerKeyPressEvent, function in the IO monad that creates the side effect of registering a key press event). mapM then executes this function once for every element in the list, and not only collects the results into a list, but collects the monadic actions into the resulting monad, meaning that the side effects from running registerKeyPressEvent 11 times are performed in order.
The final piece of the puzzle is that you might get a type error if you use mapM, because it assumes you care about the resulting list, and therefore returns m [b]. However, in this case, the type of main is IO (), and () is not going to match up to [b]. You therefore want a slight variation on mapM that throws away the resulting list, only collecting the monadic actions:
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
This has the return type you're looking for.

Related

Haskell - getting Char type when expecting [Char] within List Monad

I am practising Haskell by trying to make a program that finds .mp3 and .flac metadata and writes it neatly to a file. I've gone this far on my own but I am pretty stumped at what I should be doing. Here is the main chunk of the code here:
builddir xs = do
writeto <- lastest getArgs
let folderl b = searchable <$> (getPermissions b)
let filel c = ((lastlookup mlookup c) &&) <$> ((not <$> folderl c))
a <- listDirectory xs
listdirs <- filterM (folderl) (map ((xs ++ "/") ++) a)
filedirs <- filterM (filel) (map ((xs ++ "/") ++) a)
let tagfiles = mapM (tagsort) filedirs
putStrLn $ concat listdirs
putStrLn $ concat tagfiles
tagsort xs = do
nsartist <- getTags xs artistGetter
nsalbum <- getTags xs albumGetter
artist <- init $ drop 8 $ show nsalbum
album <- init $ drop 7 $ show nsalbum
(artist ++ " - " ++ album)
I know, it's very messy. When run in ghci, I get this:
• Couldn't match expected type ‘[Char]’ with actual type ‘Char’
• In the first argument of ‘(++)’, namely ‘artist’
In a stmt of a 'do' block: artist ++ " - " ++ album
In the expression:
do nsartist <- getTags xs artistGetter
nsalbum <- getTags xs albumGetter
artist <- init $ drop 8 $ show nsalbum
album <- init $ drop 7 $ show nsalbum
....
60 artist ++ " - " ++ album
I'm having trouble understanding why this is happening. Running a similar command in a test program of mine:
main = do
artg <- getTags "/home/spilskinanke/backlogtest/02 - await rescue.mp3" artistGetter
let test = init $ drop 8 $ show artg
print test
this works exactly fine. Prints the string "65daysofstatic" to my terminal in ghci. It clearly is not a Char type. So why is being called a Char in my code?
Also note that before adding any pieces of code that referenced the metadata module I am using (htaglib) this program ran fine in a test. With the tagfiles function and tagsort monad absent, I was able to set an arg for a certain directory, and my test would successfully print a list of FilePaths containing all readable folders, and another list of FilePaths containing all files ending in whatever I desired in mlookup, in this case being .mp3, .mp4, .flac, and .wav. Any help would be appreciated.
You’re mixing up IO and [] in tagsort:
tagsort xs = do
-- Okay, run IO action and bind result to ‘nsartist’
nsartist <- getTags xs artistGetter
-- Similarly for ‘nsalbum’
nsalbum <- getTags xs albumGetter
-- Mistaken: ‘init …’ returns a list, not an ‘IO’ action
artist <- init $ drop 8 $ show nsalbum
album <- init $ drop 7 $ show nsalbum
-- You are also missing a ‘pure’ or ‘return’ here
(artist ++ " - " ++ album)
The fixes are simple: use a let statement instead of a bind statement <-, and add a pure to make an IO String out of the String you have:
tagsort xs = do
nsartist <- getTags xs artistGetter
nsalbum <- getTags xs albumGetter
let artist = init $ drop 8 $ show nsalbum
let album = init $ drop 7 $ show nsalbum
pure (artist ++ " - " ++ album)
Generally speaking, each do block must be in a single monad, until you start learning about using monad transformers to combine different effects. So in an IO block, anything on the right of a binding statement must be an IO action; if you just want to do pure computations, you can use let (or just inline expressions, if you don’t need to bind something to a name). Finally, the last statement in a do block must also be an action in the particular monad—this is often a pure value, just wrapped up in the monad with pure :: Applicative f => a -> f a (or return :: Monad m => a -> m a, which does the same thing but works in slightly fewer contexts because of the more restrictive Monad constraint).

Pipe that maintains state

I'm trying to calculate rolling hash values (buzzhash) for a big file using pipes.
Currently I have this. But don't know how to write a pipe that maintains a state.
import qualified Data.ByteString.Lazy as L
import Data.Word
import Data.Bits(xor,rotate)
import Data.Array
import Pipes
import Control.Monad.State.Strict
import Control.Monad(forever)
produceFromList (x:xs) = do
yield x
produceFromList xs
buzzHash = do
x <- await
h <- lift $ get -- pull out previous value
let h' = rotate h 1 `xor` (hashArrW8!x) -- calculate new value
lift $ put h' -- save new value
yield h'
stdoutLn :: Consumer Word64 IO ()
stdoutLn = do
a <- await
lift $ print a
main = do
bs <- L.unpack `fmap` L.getContents
runEffect $ produceFromList bs >-> buzzHash >-> stdoutLn
hashArrW8 :: Array Word8 Word64
How do I make buzzHash save previous value and use it for the calculation of next value? Initial state value should be 0.
You were almost there; you just need to run the state.
main = do
bs <- L.unpack `fmap` L.getContents
flip execStateT 0 $ runEffect $ produceList bs >-> buzzHash >-> hoist lift stdoutLn
I assume you don't want to recover the state, so I use execStateT rather than runStateT.
The only curiosity here is that stdoutLn was marked as Consumer Word64 IO () . So I use hoist lift to make it Consumer Word64 (StateT Word64 IO) () Everything in the series a >-> b >-> c must agree in the underlying monad and return type.
Here are a few further comments that might save you time. First produceFromList is each.
Moreover, you could have avoided the hoist lift by relabeling your stdoutLn:
stdoutLn :: MonadIO m => Consumer Word64 m ()
stdoutLn = do
a <- await
liftIO $ print a
But here there is some trouble: you are not repeating the action. This should pretty clearly be a loop:
stdoutLn :: MonadIO m => Consumer Word64 m ()
stdoutLn = do
a <- await
liftIO $ print a
stdoutLn
in fact this is already available as P.print, so we can write
import qualified Pipes.Prelude as P
main = do
bs <- L.unpack `fmap` L.getContents
flip execStateT 0 $ runEffect $ each bs >-> buzzHash >-> P.print
If I understand you, buzzHash is meant to be repeated indefinitely too:
buzzHash = do
x <- await
h <- lift $ get -- pull out previous value
let h' = rotate h 1 `xor` (hashArrW8!x) -- calculate new value
lift $ put h' -- save new value
yield h'
buzzHash
(this is forever buzzHash, where we use your buzzHash)
Finally, if you
import qualified Pipes.ByteString as PB
import Control.Lens (view) -- (or Lens.Micro.MTL or Lens.Simple)
we see we don't need the lazy bytestring IO, which doesn't stream properly anyway.
Pipes.ByteString already has the unpack we want, packaged as a lens, so that we use view PB.unpack where elsewhere we would use B.unpack. So in the end we can write
main = flip evalStateT 0 $ runEffect $ view PB.unpack PB.stdin >-> buzzHash >-> P.print
Once it is in this form we see we aren't using the underlying state of the pipeline except in buzzHash, so we can localize this
import Pipes.Lift (evalStateP)
main = runEffect $ view PB.unpack PB.stdin >-> evalStateP 0 buzzHash >-> P.print
or, if you like you can rewrite
buzzHash' :: Monad m => Word64 -> Pipe Word8 Word64 m r
buzzHash' n = evalStateP n $ forever $ do
x <- await
h <- lift $ get -- pull out previous value
let h' = rotate h 1 `xor` (hashArrW8!x) -- calculate new value
lift $ put h' -- save new value
yield h'
Then you would write
main = runEffect $ view PB.unpack PB.stdin >-> buzzHash' 0 >-> P.print

Call multiple IO functions on the same input

Suppose I have a list of tuples (e.g. [(a,b)]) each a result of some previous computation.
And I want several functions to be applied on each of these elements (e.g one function might print it another send it over the network etc.)
What I've tried:
import Control.Applicative
main = do
let a = [1..5]
let fs = [(\k-> putStrLn $ show $ k*2), (\k-> putStrLn $ show $ k-2), (\k-> putStrLn $ show $ k*10)]
let res = fs <*> a
putStrLn $ "Yo"
prints just "Yo".
If you look closely res has type [IO ()] and you never use it.
So just sequence it:
main = do
let a = [1..5]
let fs = [(\k-> putStrLn $ show $ k*2), (\k-> putStrLn $ show $ k-2), (\k-> putStrLn $ show $ k*10)]
let res = fs <*> a
sequence res
putStrLn $ "Yo"
in case you want to know how you could right the complete block more concise than you could refactor the list of mappings (using sections), go with print (which is basically your putStrLn . show) and mapM_:
main = do
mapM_ print $ [(* 2), (+ (-2)), (* 10)] <*> [1..5]
putStrLn $ "Yo"
which will give
λ> :main
2
4
6
8
10
-1
0
1
2
3
10
20
30
40
50
Yo
as well ;)
note that you probably should not mix all the IO stuff with the purer computations - instead I would refactor the list of integers out:
myCombinations :: [Int] -> [Int]
myCombinations ns = [(* 2), (+ (-2)), (* 10)] <*> ns
main = do
mapM_ print $ myCombinations [1..5]
putStrLn $ "Yo"
(of course introducing functions as you go along, but I cannot guess what you are trying to achieve here)
from this you gain the ability to just check your pure functions/values:
λ> myCombinations [1..5]
[2,4,6,8,10,-1,0,1,2,3,10,20,30,40,50]
and gain probably a lot of readability ;)
If you have a list of ios :: [a -> IO b] you could use mapM ($ aValue) ios to get IO [b] or mapM_ to get IO ()
let doesn't bind anything into the monad. So IO doesn't care what you do with <*> to apply functions in a list, as long as you don't use the result in any way in a monad action.
To simply execute a list of actions in... well, sequence, you can use sequence:
let res = fs <*> a
sequence res

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.

Toggle between event handlers in gtk2hs

I would like to do something like this:
handlerOn = do
cid <- canvas `on` buttonPressEvent $ tryEvent do
signalDisconnect cid
handlerOff
putStrLn "handlerOn"
handlerOff = do
cid <- canvas `on` buttonPressEvent $ tryEvent do
signalDisconnect cid
handlerOn
putStrLn "handlerOff"
This won't work as it is, of course, because I'm trying to use cid inside a code block where cid is not assigned yet.
The idea is to register an event listener that when it receives an event, it will deregister itself and register a different event listener which will do the same, back and forth.
GHC supports recursive do.
handlerOn = do
rec cid <- canvas `on` buttonPressEvent $ tryEvent do
signalDisconnect cid
handlerOff
putStrLn "handlerOn"
You could also use Control.Monad.Fix.
handlerOff = do
mfix $ \cid -> canvas `on` buttonPressEvent $ tryEvent do
signalDisconnect cid
handlerOn
putStrLn "handlerOff"
Or manage the handler yourself.
do ...
h <- newIORef undefined
let handlerOn = do
...
writeIORef h handlerOff
handlerOff = do
...
writeIORef h handlerOn
writeIORef h handlerOn
canvas `on` buttonPressEvent $ tryEvent =<< readIORef h
Or just make everything into a single handler.
do ...
ms <- newIORef False
canvas `on` buttonPressEvent $ tryEvent do
s <- readIORef ms
if s
then ...
else ...

Resources