Servant always give me a initial value in ReaderT Monad

Zale

I'm learning Servant and write a simple service. Here's source code:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RankNTypes #-}

module BigMama where

import           Control.Concurrent
import           Control.Concurrent.STM
import           Control.Monad
import           Control.Monad.Reader
import           Data.Aeson
import           Data.Aeson.Types
import qualified Data.ByteString.Lazy.Char8 as C
import           Data.Char
import qualified Data.Map as M
import           Debug.Trace
import           GHC.Generics
import           Prelude hiding (id)
import           Servant

data MicroService = MicroService
  { name :: String
  , port :: Int
  , id :: Maybe String
  } deriving (Generic)

instance ToJSON MicroService
instance FromJSON MicroService

instance Show MicroService where
  show = C.unpack . encode

type ServiceSet = STM (TVar (M.Map String MicroService))

type LocalHandler = ReaderT ServiceSet IO

defaultServices :: ServiceSet
defaultServices = newTVar $ M.fromList []

type Api =
  "bigmama" :> Get '[JSON] (Maybe MicroService)
  :<|> "bigmama" :> ReqBody '[JSON] MicroService :> Post '[JSON] MicroService

api :: Proxy Api
api = Proxy

serverT :: ServerT Api LocalHandler
serverT = getService
  :<|> registerService

getService :: LocalHandler (Maybe MicroService)
getService = do
  stm <- ask
  liftIO . atomically $ do
    tvar <- stm
    mss <- readTVar tvar
    return $ M.lookup "file" mss

registerService :: MicroService -> LocalHandler MicroService
registerService ms = do
  stm <- ask
  liftIO . atomically $ do
    tvar <- stm
    mss <- readTVar tvar
    let mss' = M.insert (name ms) ms mss
    writeTVar tvar mss'
  return ms

readerToHandler' :: forall a. ServiceSet -> LocalHandler a -> Handler a
readerToHandler' ss r = liftIO $ runReaderT r ss

readerToHandler :: ServiceSet -> (:~>) LocalHandler Handler
readerToHandler ss = Nat (readerToHandler' ss)

server :: Server Api
server = enter (readerToHandler defaultServices) serverT

It seems like servant providing a new defaultServices for every request. I send POST to create service (name = "file") and can't get the service back on GET request. How to share data among requests on servant?

R B

It seems like servant providing a new defaultServices for every request.

It is, because your code as written is an STM action to do so. Following the logic—

defaultServices :: ServiceSet
defaultServices = newTVar ...

This (fragmentary) definition crucially does not run the STM action to produce a new TVar. Instead it defines a value (defaultServices) which is an STM action which can produce TVars. Following where defaultServices gets passed to, you use it in your handlers like—

getService = do
  stm <- ask
  liftIO . atomically $ do
    tvar <- stm
    ...

The action stored in your Reader is unchanged from the defaultServices value itself, so this code is equivalent to—

getService = do
  liftIO . atomically $ do
    tvar <- defaultServices
    ...

And by substituting in the definition of defaultServices

getService = do
  liftIO . atomically $ do
    tvar <- newTVar ...
    ...

This now looks obviously wrong. Instead of defaultServices being an action to produce a new TVar, it should be that TVar itself, right? So on the type level without aliases—

type ServiceSet = STM (TVar (M.Map String MicroService)) -- From this
type Services   =      TVar (M.Map String MicroService)  -- To this

defaultServices :: Services

Now defaultServices represents an actual TVar, instead of a method of creating TVars. Writing this may seem tricky if it's your first time because you somehow have to run an STM action, but atomically just turns that into an IO action, and you probably “know” that there is no way to escape IO. This actually is incredibly common though, and a quick look at the actual stm documentation for the functions in play will point you right to the answer.

It turns out that this is one of those exciting times in your life as a Haskell developer that you get to use unsafePerformIO. The definition of atomically spells out pretty much exactly what you have to do.

Perform a series of STM actions atomically.

You cannot use atomically inside an unsafePerformIO or unsafeInterleaveIO. Any attempt to do so will result in a runtime error. (Reason: allowing this would effectively allow a transaction inside a transaction, depending on exactly when the thunk is evaluated.)

However, see newTVarIO, which can be called inside unsafePerformIO, and which allows top-level TVars to be allocated.

Now there's one final piece of this puzzle that isn't in the documentation, which is that unless you tell GHC not to inline your top-level value produced using unsafePerformIO, you might still end up with sites where you use defaultServices having their own unique set of services. E.g., without forbidding inlining this would happen—

getService = do
  liftIO . atomically $ do
    mss <- readTVar defaultServices

getService = do
  liftIO . atomically $ do
    mss <- readTVar (unsafePerformIO $ newTVarIO ...)
    ...

This is a simple fix though, just add a NOINLINE pragma to your definition of defaultServices.

defaultServices :: Services
defaultServices = unsafePerformIO $ newTVar M.empty
{-# NOINLINE defaultServices #-}

Now this is a fine solution, and I've happily used it in production code, but there are some objections to it. Since you're already fine with using a ReaderT in your handler monad stack (and the above solution is mostly for people who for some reason are avoiding threading a reference around), you could just create a new TVar at program initialization and then pass that in. The briefest sketch of how that would work is below.

main :: IO ()
main = do
  services <- atomically (newTVar M.empty)
  run 8080 $ serve Proxy (server services)

server :: TVar Services -> Server Api
server services = enter (readerToHandler services) serverT

getService :: LocalHandler (Maybe MicroService)
getService = do
  services <- ask
  liftIO . atomically $ do
    mss <- readTVar services
    ...

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Using servant with ReaderT IO a

From Dev

Symfony form always give me "This value should not be blank." message

From Dev

How to give an initial value to NSManaged property in CoreData?

From Dev

Difftime function always give me 0

From Dev

How to get ReaderT to work with another monad transformer?

From Dev

How to get ReaderT to work with another monad transformer?

From Dev

Getting Initial Value for LiveData Always Returning Null

From Dev

Does [NSDate distantPast] always give same value?

From Dev

Using failWith with Servant and custom monad stack

From Dev

Using Authentication With a Custom Reader Monad With Servant

From Dev

Using Authentication With a Custom Reader Monad With Servant

From Dev

Using failWith with Servant and custom monad stack

From Dev

Are dimensioned variables only re-dimensioned if you give it an initial value?

From Dev

Convert int to float and back doesn't give initial value

From Dev

imagemagick php writeImages always give me 1 page result

From Dev

Alamofire always give me error with status code 500 in response

From Dev

Why does "_" not always give me the last result in interactive shell

From Dev

Why does the HttpClient always give me the same response?

From Dev

looping through the button always give me 0 answer

From Dev

Deserialize only give me 0 as value

From Dev

The value from final variable give me minus

From Dev

guard statement variable do not give me value

From Dev

How do I use list monad inside of ReaderT?

From Dev

How do I use list monad inside of ReaderT?

From Dev

React functional component state always logs initial value

From Dev

Give ListChangeListener initial state

From Dev

I am getting sum of last index but initial index give me 0 why?

From Dev

getStringArrayListExtra give me a null value but I have values on it

From Dev

Why does using the '~' operator in scala give me a negative value

Related Related

  1. 1

    Using servant with ReaderT IO a

  2. 2

    Symfony form always give me "This value should not be blank." message

  3. 3

    How to give an initial value to NSManaged property in CoreData?

  4. 4

    Difftime function always give me 0

  5. 5

    How to get ReaderT to work with another monad transformer?

  6. 6

    How to get ReaderT to work with another monad transformer?

  7. 7

    Getting Initial Value for LiveData Always Returning Null

  8. 8

    Does [NSDate distantPast] always give same value?

  9. 9

    Using failWith with Servant and custom monad stack

  10. 10

    Using Authentication With a Custom Reader Monad With Servant

  11. 11

    Using Authentication With a Custom Reader Monad With Servant

  12. 12

    Using failWith with Servant and custom monad stack

  13. 13

    Are dimensioned variables only re-dimensioned if you give it an initial value?

  14. 14

    Convert int to float and back doesn't give initial value

  15. 15

    imagemagick php writeImages always give me 1 page result

  16. 16

    Alamofire always give me error with status code 500 in response

  17. 17

    Why does "_" not always give me the last result in interactive shell

  18. 18

    Why does the HttpClient always give me the same response?

  19. 19

    looping through the button always give me 0 answer

  20. 20

    Deserialize only give me 0 as value

  21. 21

    The value from final variable give me minus

  22. 22

    guard statement variable do not give me value

  23. 23

    How do I use list monad inside of ReaderT?

  24. 24

    How do I use list monad inside of ReaderT?

  25. 25

    React functional component state always logs initial value

  26. 26

    Give ListChangeListener initial state

  27. 27

    I am getting sum of last index but initial index give me 0 why?

  28. 28

    getStringArrayListExtra give me a null value but I have values on it

  29. 29

    Why does using the '~' operator in scala give me a negative value

HotTag

Archive