
I've written a very simple daemon, and it's working just fine. But I'd appreciate it if someone could take a look at it and tell me if any of the code I've written is... stupid. (I'm trying to get more comfortable with this monad stuff.) Thank you in advance. Daemon.hs --------- module Daemon where import System.Exit import System.IO.Unsafe import System.Posix.Signals import Control.Concurrent class DaemonState a where initialise :: IO a work :: a -> IO a finalise :: a -> IO () termReceived = unsafePerformIO (newMVar False) handleTERM :: IO () handleTERM = swapMVar termReceived True >> return () loop :: (DaemonState a) => a -> IO (Maybe a) loop d = do timeToStop <- readMVar termReceived if timeToStop then finalise d >> return Nothing else work d >>= loop start :: (DaemonState a) => IO (Maybe a) start = installHandler sigTERM (Catch handleTERM) Nothing >> initialise >>= loop ----- Here's a simple example of how I use it ----- Main.hs ------- module Main where import Daemon instance DaemonState Int where initialise = do putStrLn "Starting up" return 0 work i = do putStrLn (show i) return (i+1) finalise i = do putStrLn "Shutting down" return () main = start :: IO (Maybe Int)

On Tue, Nov 16, 2010 at 2:18 AM, Amy de Buitléir
I've written a very simple daemon, and it's working just fine. But I'd appreciate it if someone could take a look at it and tell me if any of the code I've written is... stupid. (I'm trying to get more comfortable with this monad stuff.) Thank you in advance.
Daemon.hs --------- module Daemon where
import System.Exit import System.IO.Unsafe import System.Posix.Signals import Control.Concurrent
class DaemonState a where initialise :: IO a work :: a -> IO a finalise :: a -> IO ()
To use a typeclass there is pretty strange and doesn't have much advantage, it would be much easier and nicer to just write start to take some functions as parameters (why would there be only one instance for each type...). In other words it works but is only a perversion of the idea of typeclass without any practical advantages. On the other hand if you had some strange pedagogical requirements, feel free to ignore this ! -- Jedaï

Chaddaï Fouché
To use a typeclass there is pretty strange and doesn't have much advantage, it would be much easier and nicer to just write start to take some functions as parameters...
Doh! Yes, I kind of overlooked the obvious there. Thank you so much! Here's my new version, in case anyone Googles this. ----- Daemon.hs ----- module Daemon where import System.Exit import System.IO.Unsafe import System.Posix.Signals import Control.Concurrent termReceived = unsafePerformIO (newMVar False) handleTERM :: IO () handleTERM = swapMVar termReceived True >> return () loop :: (a -> IO a) -> (a -> IO ()) -> a -> IO (Maybe a) loop work finalise d = do timeToStop <- readMVar termReceived if timeToStop then finalise d >> return Nothing else work d >>= loop work finalise start -- | This function will be invoked when the daemon starts. :: IO a -- | This function will be invoked in the main loop. -> (a -> IO a) -- | This function will be invoked when the daemon shuts down. -> (a -> IO ()) -- | The result will always be Nothing. -> IO (Maybe a) start initialise work finalise = installHandler sigTERM (Catch handleTERM) Nothing >> initialise >>= loop work finalise ----- CountingExample.hs ----- module CountingExample where import Daemon initialise = do putStrLn "Starting up" return 0 work i = do putStrLn (show i) return (i+1) finalise i = do putStrLn "Shutting down" return () main = start initialise work finalise

Amy,
Here's a small suggestion:
If the start and loop functions always returns Nothing, I think it may
be cleaner to
simply make them return ().
Patrick
On Tue, Nov 16, 2010 at 3:28 PM, Amy de Buitléir
Chaddaï Fouché
writes: To use a typeclass there is pretty strange and doesn't have much advantage, it would be much easier and nicer to just write start to take some functions as parameters...
Doh! Yes, I kind of overlooked the obvious there. Thank you so much! Here's my new version, in case anyone Googles this.
----- Daemon.hs ----- module Daemon where
import System.Exit import System.IO.Unsafe import System.Posix.Signals import Control.Concurrent
termReceived = unsafePerformIO (newMVar False)
handleTERM :: IO () handleTERM = swapMVar termReceived True >> return ()
loop :: (a -> IO a) -> (a -> IO ()) -> a -> IO (Maybe a) loop work finalise d = do timeToStop <- readMVar termReceived if timeToStop then finalise d >> return Nothing else work d >>= loop work finalise
start -- | This function will be invoked when the daemon starts. :: IO a -- | This function will be invoked in the main loop. -> (a -> IO a) -- | This function will be invoked when the daemon shuts down. -> (a -> IO ()) -- | The result will always be Nothing. -> IO (Maybe a) start initialise work finalise = installHandler sigTERM (Catch handleTERM) Nothing >> initialise >>= loop work finalise
----- CountingExample.hs -----
module CountingExample where
import Daemon
initialise = do putStrLn "Starting up" return 0
work i = do putStrLn (show i) return (i+1)
finalise i = do putStrLn "Shutting down" return ()
main = start initialise work finalise
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
-- ===================== Patrick LeBoutillier Rosemère, Québec, Canada

Patrick LeBoutillier
Amy,
Here's a small suggestion:
If the start and loop functions always returns Nothing, I think it may be cleaner to simply make them return ().
Thank you Patrick, that make the interface easier for the user to understand. The "loop" function does return something when it is called recursively, but the outer call never returns anything, so my first try was to have two separate functions. ----- code snippet ----- loop :: (a -> IO a) -> (a -> IO ()) -> a -> IO () loop work finalise d = loop' work finalise d >> return () loop' :: (a -> IO a) -> (a -> IO ()) -> a -> IO (Maybe a) loop' work finalise d = do timeToStop <- readMVar termReceived if timeToStop then finalise d >> return Nothing else work d >>= loop' work finalise ----- end of code snippet ----- But then I thought that looked more complicated, so I went with this. (CountingExample.hs is unchanged.) ----- Daemon.hs ----- module Daemon where import System.Exit import System.IO.Unsafe import System.Posix.Signals import Control.Concurrent termReceived = unsafePerformIO (newMVar False) handleTERM :: IO () handleTERM = swapMVar termReceived True >> return () loop :: (a -> IO a) -> (a -> IO ()) -> a -> IO (Maybe a) loop work finalise d = do timeToStop <- readMVar termReceived if timeToStop then finalise d >> return Nothing else work d >>= loop work finalise start -- | This function will be invoked when the daemon starts. :: IO a -- | This function will be invoked in the main loop. -> (a -> IO a) -- | This function will be invoked when the daemon shuts down. -> (a -> IO ()) -- | Returns nothing. -> IO () start initialise work finalise = installHandler sigTERM (Catch handleTERM) Nothing >> initialise >>= loop work finalise >> return ()

On Tue, Nov 16, 2010 at 10:11 PM, Amy de Buitléir
Patrick LeBoutillier
writes: Amy,
Here's a small suggestion:
If the start and loop functions always returns Nothing, I think it may be cleaner to simply make them return ().
Thank you Patrick, that make the interface easier for the user to understand.
The "loop" function does return something when it is called recursively, but the outer call never returns anything, so my first try was to have two separate functions.
Even recursively it doesn't return anything, or rather it always ends up returning Nothing since that's what the base case returns and the recursive case don't modify it, also finalise already returns (), so you can write :
loop :: (a -> IO a) -> (a -> IO ()) -> a -> IO () loop work finalise d = do timeToStop <- readMVar termReceived if timeToStop then finalise d else work d >>= loop work finalise
To avoid the need to repeat the same parameters at each call of the recursive function, you can use a "worker-wrapper" (? not sure of the term) transformation :
loop :: (a -> IO a) -> (a -> IO ()) -> a -> IO () loop work finalise d = loop' d where loop' d = do timeToStop <- readMVar termReceived if timeToStop then finalise d else work d >>= loop'
Which finally can be directly included in your start function :
start -- | This function will be invoked when the daemon starts. :: IO a -- | This function will be invoked in the main loop. -> (a -> IO a) -- | This function will be invoked when the daemon shuts down. -> (a -> IO ()) -- | Returns nothing. -> IO () start initialise work finalise = installHandler sigTERM (Catch handleTERM) Nothing >> initialise >>= loop where loop d = do timeToStop <- readMVar termReceived if timeToStop then finalise d else work d >>= loop
Defining functions at the top level instead may be clearer or more useful in certain cases, but I'm not sure this is one of those cases (you don't want to export loop, start and loop bodies are very short, and you can simplify and optimize loop body by making it a local function). -- Jedaï
participants (3)
-
Amy de Buitléir
-
Chaddaï Fouché
-
Patrick LeBoutillier