Rotating backdrop (aka learning Haskell)

To help me learn Haskell, I decided on a simple (AH!) problem: given a list of images, display a random one as my desktop backdrop. After some time, change the image. Simple? What I actually want to do is a little more specific: Read a list of images (one per line) from a file. Given that a working day is about 8 hours, I want to see all the images during the day. So, the time between changes should be (nbr_of_images) / (8 * 60 * 60) seconds. Of course, if the file changes (I add or remove any number of images) this need to change and be recalculated. Clearly, I want some interaction with a pseudo-random number generator. Because this is a learning exercise, I want to have a pretty GUI for this. Three buttons: Exit (which quits the application), Reset (re-reads the file whether it changed or not) and Next (display the next image). Then I want a counter and a progress bar telling me when the next change will occur. 1- Get a list out of a file: I managed to do that using the following: parseImageFile :: FilePath -> IO [String] parseImageFile file = do inpStr <- readFile file return $ filter (/="") (breaks (=='\n') inpStr) Nice, simple and I understand what it is doing. 2- Get a random element from a list and remove it: Okay, this I understand less well. I looked at the solutions of problems 23 and 20 in http://www.haskell.org/haskellwiki/99_questions so there is a skeleton there. However, my list is IO [String].... Hum, monads. Any pointers as to how to do that? 3- Wait and do something later.... How, I have no idea how to do that! Help? 4- I guess that progress bars and updating text will be somewhere in the GUI (I chose wxHaskell)... Again, no idea where. 5- How do you call an external program in Haskell? Either xv or Esetroot will do the job of displaying the image. Is there a Haskell native way to do that? Once this is done and I have commented to the code, I will be happy to put it onto the wiki as a teaching aid. Thanks. -- yann@kierun.org -= H+ =- www.kierun.org PGP: 009D 7287 C4A7 FD4F 1680 06E4 F751 7006 9DE2 6318

Yann Golanski
1- Get a list out of a file: I managed to do that using the following:
parseImageFile :: FilePath -> IO [String] parseImageFile file = do inpStr <- readFile file return $ filter (/="") (breaks (=='\n') inpStr)
Nice, simple and I understand what it is doing.
Can be improved: breaks (=='\n') == lines -- easier to read, no? filter (/="") == filter (not . null) -- more polymorphic, not important here do x <- expr1 == expr1 >>= return . expr2 return $ expr2 x -- i.e. "readFile f >>= return . filter (not.null) . lines"
2- Get a random element from a list and remove it: Okay, this I understand less well. I looked at the solutions of problems 23 and 20 in http://www.haskell.org/haskellwiki/99_questions so there is a skeleton there. However, my list is IO [String].... Hum, monads.
Since you really want to shuffle the list, how about assigning each entry a random number, and sorting the list? shuffle :: [String] -> IO [String] shuffle fs = do rs <- randoms return (map snd $ sort $ zipWith rs fs)
5- How do you call an external program in Haskell?
See the 'System.Process' or 'System.Cmd' modules. -k -- If I haven't seen further, it is by standing in the footprints of giants

On Tue, 2008-05-20 at 10:55 +0200, Ketil Malde wrote:
Yann Golanski
writes: 1- Get a list out of a file: I managed to do that using the following:
parseImageFile :: FilePath -> IO [String] parseImageFile file = do inpStr <- readFile file return $ filter (/="") (breaks (=='\n') inpStr)
Nice, simple and I understand what it is doing.
Can be improved:
breaks (=='\n') == lines -- easier to read, no? filter (/="") == filter (not . null) -- more polymorphic, not important here do x <- expr1 == expr1 >>= return . expr2 return $ expr2 x -- i.e. "readFile f >>= return . filter (not.null) . lines"
do x <- expr1; return $ expr2 x == expr1 >>= return . expr2 == liftM expr2 expr1 -- or fmap (a.k.a. <$>) if you like So, liftM (filter (not . null) . lines) readFile alternatively, filter (not . null) . lines <$> readFile

Quoth Derek Elkins on Tue, May 20, 2008 at 11:45:57 -0500
On Tue, 2008-05-20 at 10:55 +0200, Ketil Malde wrote:
Yann Golanski
writes: 1- Get a list out of a file: I managed to do that using the following:
parseImageFile :: FilePath -> IO [String] parseImageFile file = do inpStr <- readFile file return $ filter (/="") (breaks (=='\n') inpStr)
Nice, simple and I understand what it is doing.
Can be improved:
breaks (=='\n') == lines -- easier to read, no? filter (/="") == filter (not . null) -- more polymorphic, not important here do x <- expr1 == expr1 >>= return . expr2 return $ expr2 x -- i.e. "readFile f >>= return . filter (not.null) . lines"
do x <- expr1; return $ expr2 x == expr1 >>= return . expr2 == liftM expr2 expr1 -- or fmap (a.k.a. <$>) if you like
So, liftM (filter (not . null) . lines) readFile alternatively, filter (not . null) . lines <$> readFile
I'm sorry, this is a little beyond me. Could you elaborate a little more on what this actually does? -- yann@kierun.org -= H+ =- www.kierun.org PGP: 009D 7287 C4A7 FD4F 1680 06E4 F751 7006 9DE2 6318

On Tue, May 20, 2008 at 09:15:57AM +0100, Yann Golanski wrote:
To help me learn Haskell, I decided on a simple (AH!) problem: given a list of images, display a random one as my desktop backdrop. After some time, change the image. Simple?
What I actually want to do is a little more specific: Read a list of images (one per line) from a file. Given that a working day is about 8 hours, I want to see all the images during the day. So, the time between changes should be (nbr_of_images) / (8 * 60 * 60) seconds. Of course, if the file changes (I add or remove any number of images) this need to change and be recalculated. Clearly, I want some interaction with a pseudo-random number generator.
Because this is a learning exercise, I want to have a pretty GUI for this. Three buttons: Exit (which quits the application), Reset (re-reads the file whether it changed or not) and Next (display the next image). Then I want a counter and a progress bar telling me when the next change will occur.
1- Get a list out of a file: I managed to do that using the following:
parseImageFile :: FilePath -> IO [String] parseImageFile file = do inpStr <- readFile file return $ filter (/="") (breaks (=='\n') inpStr)
Nice, simple and I understand what it is doing.
2- Get a random element from a list and remove it: Okay, this I understand less well. I looked at the solutions of problems 23 and 20 in http://www.haskell.org/haskellwiki/99_questions so there is a skeleton there. However, my list is IO [String].... Hum, monads.
Any pointers as to how to do that?
3- Wait and do something later.... How, I have no idea how to do that! Help?
One way is System.Concurrent.threadDelay, though there might be another way that integrates more nicely with wxHaskell. Hopefully you can find it in their documentation. http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurren...
4- I guess that progress bars and updating text will be somewhere in the GUI (I chose wxHaskell)... Again, no idea where.
5- How do you call an external program in Haskell? Either xv or Esetroot will do the job of displaying the image. Is there a Haskell native way to do that?
Try the System.Cmd.system function. http://www.haskell.org/ghc/docs/latest/html/libraries/process/System-Cmd.htm...
Once this is done and I have commented to the code, I will be happy to put it onto the wiki as a teaching aid.
Thanks.
-- yann@kierun.org -= H+ =- www.kierun.org PGP: 009D 7287 C4A7 FD4F 1680 06E4 F751 7006 9DE2 6318
Cheers, Spencer Janssen

Hello Yann, Tuesday, May 20, 2008, 12:15:57 PM, you wrote:
To help me learn Haskell, I decided on a simple (AH!) problem: given a list of images, display a random one as my desktop backdrop. After some time, change the image. Simple?
what you try to implements looks like too large problem for one step in learning, i suggest to split it into two parts - first, write console app that does all the stuff, and then add GUI frontend to it for learning IO, i can suggest http://haskell.org/haskellwiki/IO_inside although it's rather large and deep tutorial while wxHaskell is easier to use, for learning purposes gtk2hs may be better since it has good tutorial a few links for gtk2hs: Home: http://haskell.org/gtk2hs/overview/ Paper: http://haskell.org/~shae/memory.pdf Glade intro: http://haskell.org/gtk2hs/docs/tutorial/glade/ Tutorial: http://home.telfort.nl/sp969709/gtk2hs/index.html the same for wxHaskell: paper about high-level wxHaskell features: http://research.microsoft.com/users/daan/download/papers/wxhaskell.pdf slides on the same topic: http://www.cse.unsw.edu.au/~cs4132/lecture/wlta543.pdf -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Rather than golfing your questions (which seems popular), I'll try to
give you some concrete answers.
First off, I agree with Bulat that this is probably too large of a
first project; doing it in pieces seems like it would be a lot
simpler. In particular "if the file changes, ..." isn't a requirement
I'd put on the project for a "first attempt". And just getting the
non-GUI version to work is a good start; then you can go about
learning a GUI library, which can be a project all by itself :)
2008/5/20 Yann Golanski
2- Get a random element from a list and remove it: Okay, this I understand less well. I looked at the solutions of problems 23 and 20 in http://www.haskell.org/haskellwiki/99_questions so there is a skeleton there. However, my list is IO [String].... Hum, monads.
There's nothing tricky about that, though (although 'and remove it' is a bit odd from a Haskeller's point of view; you would instead make a new list without that element). Lets break this out into some a function to solve the problem. I'll leave the implementation to you. removeElement :: [String] -> Int -> (String, [String]) This is a pure function, but you are, as you mentioned, inside IO. But that's no problem; in your higher level code (where you call parseImageFile) you should also be inside IO: main = do images <- parseImageFile path -- now, images :: [String] -- simple! you are living inside of IO here, so you can get at the values returned! n <- getRandomNumber (length images) let (image, rest) = removeElement images n changeDesktop image Note the difference between the lines with "<-" and the line with "let"; "x <- m" takes "m :: IO a" and gives you an "x :: a". Whereas "let x = z" takes "z :: a" and gives "x :: a"; it just labels the result of a non-IO call. Now you just have to write the other functions: getRandomNumber :: Int -> IO Int changeDesktop :: String -> IO () path :: String -- not really a function! To loop, you can write an IO action that calls itself as the last element of the "do" statement. -- ryan

On Tue, May 20, 2008 at 09:15:57AM +0100, Yann Golanski wrote:
1- Get a list out of a file: I managed to do that using the following:
parseImageFile :: FilePath -> IO [String] parseImageFile file = do inpStr <- readFile file return $ filter (/="") (breaks (=='\n') inpStr)
Note that there is a standard function "lines" which splits a string into lines.
Nice, simple and I understand what it is doing.
2- Get a random element from a list and remove it: Okay, this I understand less well. I looked at the solutions of problems 23 and 20 in http://www.haskell.org/haskellwiki/99_questions so there is a skeleton there. However, my list is IO [String].... Hum, monads.
Any pointers as to how to do that?
import System.Random removeRandomElement :: [a] -> IO (a, [a]) removeRandomElement l = do i <- randomRIO (0, length l - 1) return (removeAt i l) where removeAt is from problem 20 above. And you use it like anything else in the IO monad: do ... images <- parseImageFile ... ... (chosen, rest) <- removeRandomElement images ...
3- Wait and do something later.... How, I have no idea how to do that! Help?
Control.Concurrent.threadDelay is the simplest way to make a thread sleep for a while. However, if you're using some GUI library, you may want to use the library's own timers.
4- I guess that progress bars and updating text will be somewhere in the GUI (I chose wxHaskell)... Again, no idea where.
I'm not familiar with wxHaskell, sorry.
5- How do you call an external program in Haskell? Either xv or Esetroot will do the job of displaying the image. Is there a Haskell native way to do that?
There is a direct X11 library for Haskell, but you're probably better off just calling an external program. See the System.Process module. Hope this helps. Lauri

First version is available at: http://www.kierun.org/backdropper-1.0.tbz2 It is minimal but does the trick of randomly rotating backgrounds. Features to add are only rotate during work hours, making sure that all images are shown within a day and a nice GUI. Criticism welcome. -- yann@kierun.org -= H+ =- www.kierun.org PGP: 009D 7287 C4A7 FD4F 1680 06E4 F751 7006 9DE2 6318
participants (7)
-
Bulat Ziganshin
-
Derek Elkins
-
Ketil Malde
-
Lauri Alanko
-
Ryan Ingram
-
Spencer Janssen
-
Yann Golanski