How to implement a state manager?

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:

exports.jsonMiddleware = require('express').json();
3 Likes

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
3 Likes

Yes, I am aware of this, so I have been confused.

I checked Effect.Ref, which is exactly what I need!

thank you all.

1 Like