(Haskell) gi-gtk setting image in button callback

Viewed 88

I followed a tutorial on creating a basic Gi-Gtk application. Now I want to react to a button press by setting the source of an Image to a String which I randomly choose from a constant List of Strings:

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where

import Data.GI.Base
import System.Random 
import qualified GI.Gtk as Gtk
import Control.Monad.Random

akkorde = ["C11.png","C13.png","C69.png","C6.png","C7#11.png","C7#9.png","C7b13.png","C7b9.png","C7.png","C9.png","Cadd9.png","Cj7.png"]

selectAcc:: (MonadRandom m) => m [Char]
selectAcc = do 
  let n = length akkorde  
  i <- getRandomR (0, n-1)
  return (akkorde !! i)
main :: IO ()
main = do 
  name <- selectAcc
  Gtk.init Nothing
  win <- Gtk.windowNew Gtk.WindowTypeToplevel
  Gtk.windowSetTitle win "accordtrainer"
  Gtk.onWidgetDestroy win Gtk.mainQuit
  #resize win 640 480
  
  img <- Gtk.imageNewFromFile ("../" ++ name)   
  box <- new Gtk.Box [#orientation := Gtk.OrientationVertical ]
  #add box img
  #add win box
    
  msg <- new Gtk.Label[#label := ( "")]
  #packStart box msg True False 10
 
  btn <- new Gtk.Button [#label := "Click me!"]
  #packStart box btn False False 10
  on btn #clicked ( Gtk.imageSetFromFile img  (do { name <- selectAcc; return ("../" ++ name) }))
  Gtk.widgetShowAll win
  Gtk.main

the Problem arises at the line

  on btn #clicked ( Gtk.imageSetFromFile img  (do { name <- selectAcc; return ("../" ++ name) }))

I think Gtk.imageSetFromFile expects a Maybe[Char] but currently its only getting [Char] ghc says:

app/Main.hs:38:62: error:
    • No instance for (MonadRandom Maybe)
        arising from a use of ‘selectAcc’

the Just constructor should give me a maybe type

  on btn #clicked ( Gtk.imageSetFromFile img  (Just(do { name <- selectAcc; return ("../" ++ name) })))

but then I get a type missmatch

• Couldn't match type ‘[Char]’ with ‘Char’
  Expected type: Maybe [Char]
    Actual type: Maybe [[Char]]

the chain of errors/"fixes" continues

I think Im doing something fundamentally wrong but I dont know how to do this the "correct" way if you know how I should write this line or a better way to get to the randomly selected string please respond

1 Answers

You're already working with mtl-style monad constraints which are even more advanced than normal monads, so don't worry about not understanding everything.

The simplified type of imageSetFromFile is:

imageSetFromFile :: Image -> Maybe [Char] -> IO ()

So it expects you to provide a Maybe [Char] as input where a Nothing presumably removes the image and a Just someFile sets the image to that file location.

The function that you are using has the type:

selectAcc:: MonadRandom m => m [Char]

This means that m here needs to be some monad which can generate random numbers. You can find instances of MonadRandom by typing this in GHCi:

ghci> import Control.Monad.Random
ghci> :i MonadRandom
type MonadRandom :: (* -> *) -> Constraint
class Monad m => MonadRandom m where
  getRandomR :: Random a => (a, a) -> m a
  getRandom :: Random a => m a
  getRandomRs :: Random a => (a, a) -> m [a]
  getRandoms :: Random a => m [a]
  {-# MINIMAL getRandomR, getRandom, getRandomRs, getRandoms #-}
    -- Defined in ‘Control.Monad.Random.Class’
instance (RandomGen g, Monad m) => MonadRandom (RandT g m)
  -- Defined in ‘Control.Monad.Trans.Random.Lazy’
instance [safe] MonadRandom IO
  -- Defined in ‘Control.Monad.Random.Class’

At the bottom you see two instances: something with RandT (a monad transformer) and another one with IO. However, imageSetFromFile requires a Maybe [Char] as argument, so neither will immediately work. But you can in this case generate the random name right before calling imageSetFromFile in the IO monad:

  on btn #clicked $ do
    name <- selectAcc
    Gtk.imageSetFromFile img (Just ("../" ++ name))
Related