Hello, I want to know how to implement a state manager using purescript.
The simplest requirement is this:
The program accepts htpp requests
There are two types of http request, which are used for request value and setting value respectively
State is stored in memory
This is very easy to implement in JavaScript:
var express = require("express");
var app = express();
var port = 3000;
var state = {};
app.post("/setValue", (req, res) => {
var { name, value } = req.body;
state[name] = value;
res.send("ok");
});
app.post("/getValue", (req, res) => {
var { name } = req.body;
res.send(state[name]);
});
app.listen(port);
But how to implement it in purescirpt?
Although there is a State module, I don’t understand how to use it in http callback events.
If you can, please give me a minimal example.
Thanks in advance.
For this application, I would recommend Effect.Ref for long-lived, in-memory mutable state.
Here’s a translation of your example to PureScript. For a real application, I would recommend setting up your own data types instead of such liberal use of unsafeFromForeign, but for learning purposes this’ll do.
module Main where
import Prelude
import Data.Map (Map)
import Data.Map as Map
import Data.Maybe (maybe)
import Effect (Effect)
import Effect.Class (liftEffect)
import Effect.Console (log)
import Effect.Ref (Ref)
import Effect.Ref as Ref
import Foreign (Foreign, unsafeFromForeign)
import Node.Express.App (App)
import Node.Express.App as App
import Node.Express.Request as Request
import Node.Express.Response as Response
import Node.Express.Types (Middleware)
foreign import jsonMiddleware :: Middleware
makeApp :: Ref (Map String Foreign) -> App
makeApp stateRef = do
App.useExternal jsonMiddleware
App.post "/setValue" do
{ name, value } <- unsafeFromForeign <$> Request.getBody'
liftEffect $ Ref.modify_ (Map.insert name value) stateRef
Response.send "ok"
App.post "/getValue" do
{ name } <- unsafeFromForeign <$> Request.getBody'
state <- liftEffect $ Ref.read stateRef
maybe (Response.setStatus 404 *> Response.send "404 not found") Response.sendJson $ Map.lookup name state
main :: Effect Unit
main = do
stateRef <- Ref.new Map.empty
void $ App.listenHttp (makeApp stateRef) 3000 \_ -> log "Listening... Press Ctrl-C to quit"
To make the JSON middleware work, you’ll also need a small foreign file to accompany the above module:
What you are trying to do is a bit at odds with the “pure” in PureScript, given that it relies on global mutation of the state value. That said, it is possible using Effect.Ref, as mentioned above.
Even though @rhendric already posted a solution, here’s my take on it (using Peregrine):
module Main where
import Prelude
import Data.Map as Map
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Class (liftEffect)
import Effect.Console (log)
import Effect.Ref as Ref
import Peregrine (choose)
import Peregrine as Peregrine
import Peregrine.Body (json)
import Peregrine.Http.Method (Method(..))
import Peregrine.Response as Response
import Peregrine.Routing (method, path)
main :: Effect (Effect Unit -> Effect Unit)
main = do
state <- Ref.new Map.empty
let
app = choose
[ method Post
$ path "/setValue"
$ json \({ name, value } :: { name :: String, value :: String }) _req -> do
_ <- liftEffect $ state # Ref.modify (Map.insert name value)
pure $ Just $ Response.ok
, method Post
$ path "/getValue"
$ json \({ name } :: { name :: String }) _req -> do
state' <- liftEffect $ Ref.read state
pure $ Just $
case state' # Map.lookup name of
Just value -> Response.ok # Response.text value
Nothing -> Response.notFound
]
Peregrine.fly app do
log "State Manager example server listening at http://localhost:3000"
And here it is in action:
> curl "http://localhost:3000/setValue" -H "Content-Type: application/json" -d '{"name":"hello","value":"world"}'
OK
> curl "http://localhost:3000/getValue" -H "Content-Type: application/json" -d '{"name":"hello"}'
world