0

I want to write a simple webserver in haskell which provides the current time. The time should be returned in json format.

Here is what I have so far:

{-# LANGUAGE DeriveDataTypeable #-}

import Happstack.Server
import Text.JSON.Generic
import Data.Time
import System.IO.Unsafe

data TimeStr = TimeStr {time :: String} deriving (Data, Typeable)

main = simpleHTTP nullConf $ ok $ toResponse $ encodeJSON (TimeStr $ show (unsafePerformIO getCurrentTime)) 

I am aware that unsafePerformIO should be avoided, yet I could not find a better solution yet. Maybe this is where the problem lies? I have a very basic understanding of monads.

The result is the following:

{"time":"2014-10-16 16:11:38.834251 UTC"}

The problem is that when I refresh localhost:8000 the time doesn't change. Is there some sort of memoization going on?

fuji
  • 1,173
  • 1
  • 10
  • 27
  • 2
    I can almost guarantee that `unsafePerformIO` is the source of your problem. The compiler probably thinks it's OK to run that just once, and keep the result forever. After all, it's pure, right? – MathematicalOrchid Oct 16 '14 at 16:37
  • This makes sense. I tried binding `getCurrentTime` using `<-` in order to get rid of `performUnsafeIO` but as expected this does not solve the problem. Would I need to lift the *happstack* functions? – fuji Oct 16 '14 at 16:43
  • Have you tried `liftIO` on `getCurrentTime`? I believe HappStack uses monad transformers, and I'd imagine that its top-level monad is an instance of `MonadIO`. – bheklilr Oct 16 '14 at 16:52
  • 1
    "I am aware that unsafePerformIO should be avoided" - Usually the only valid uses for `unsafePerformIO` is when interfacing with a C dll or when working on some serious internal stuff that 99.999% of haskell developers will never see. – bheklilr Oct 16 '14 at 16:54

1 Answers1

5
unsafePerformIO :: IO a -> a

This is the "back door" into the IO monad, allowing IO computation to be performed at any time. For this to be safe, the IO computation should be free of side effects and independent of its environment.

getCurrentTime is dependent on its environment, so unsafePerformIO is not the way to go. However, given a MonadIO, we can use liftIO in order to lift the action into the appropriate monad. Lets have a look at the types to find out where we can plug it in:

-- http://hackage.haskell.org/package/happstack-server-7.3.9/docs/Happstack-Server-SimpleHTTP.html
simpleHTTP :: ToMessage a => Conf -> ServerPartT IO a -> IO () 

ServerPartT is an instance of MonadIO, so we could definitely plug it in here. Lets check ok:

ok :: FilterMonad Response m => a -> m a 
--                       nope: ^^^

So we really need to get the current time before we prepare the response. After all, this makes sense: when you create the response, all heavy work has been done, you know what response code you can use and you don't need to check whether the file or entry in the database exists. After all, you were going to sent an 200 OK, right?

This leaves us with the following solution:

{-# LANGUAGE DeriveDataTypeable #-}

import Happstack.Server
import Text.JSON.Generic
import Data.Time
import System.IO.Unsafe
import Control.Monad.IO.Class (liftIO)

data TimeStr = TimeStr {time :: String} deriving (Data, Typeable)

main = simpleHTTP nullConf $ do
           currentTime <- liftIO getCurrentTime
           ok $ toResponse $ encodeJSON (TimeStr $ show currentTime)

Lessons learned

  1. Don't use unsafePerformIO.
  2. Don't use unsafePerformIO, unless you're really sure what you're actually doing.
  3. Use liftIO if you want to use an IO action in an instance of MonadIO.
user2407038
  • 14,400
  • 3
  • 29
  • 42
Zeta
  • 103,620
  • 13
  • 194
  • 236