Redox - since it mixes well with Thermite ;)
This is redux type store, but instead of forcing you to write interpreter
as a reducer you are free (or rather cofree ;)) to write it the way you want.
The library will give you different schemes how the store is updated.  Now
there is only one Redox.DSL, but in near future there will be at least one
more using coroutines (similar to how
Thermite updates react
component state).
A DSL has to be interpreted in the Aff monad.  Since Aff has an instance of
MonadEff this does not restrict you in any way.  Checkout tests how to write
synchronous and asynchronous commands
In general if your DSL is generated by a functor C (for commands):
type DSL = Free Cthen you have to find a functor RunC eff which pairs with C:
pair :: forall eff x y. C (x -> y) -> RunC eff x -> Aff eff yYou can deduce from pair's type that if C is a sum type then RunC is
a product - that's how it interprets C.
Then the interpreter has type:
type Interp eff a = Cofree (RunC eff)This give rise to a function
runInterp :: forall state. DSL(state -> state) -> RunC eff state -> Aff eff state
runInterp cmds state = exploreM pair cmds $ mkInterp stateYou can feed this function into Redox.DSL.dispatch:
dispatchS :: forall eff state. DSL(state -> state) -> Aff (redox :: Redox | eff) state
dispatchS = Redox.DSL.dispatch (\_ _ -> pure unit) runInterp storeCheck out tests for an example or this
repo.  However you can write
an interpreter without Cofree, simply by using State to track the state, or
just by hand.  The advantage of using Cofree is that whenever you will change
C the compiler will force you to update RunC in compatible way.
The Redox.DSL.dispatch function will dispatch changes to the store when the
Aff computation resolves.  You may want to dispatch every node of your
interpreter i.e. when each DSL command is run in the do block`. For example
if you try to
dispatch do
  cmd1 arg1
  cmd2 arg2The dispatch will update the store when cmd2 finishes.  But you can build
this into the interpreter.  Since this is common, there is a function in Redox.Utils to
modify an interpreter of type Cofree f a so that it updates the store on
every step of the Cofree comonad:
Redox.Utils.mkIncInterp
  :: forall state f
   . (Functor f)
  => Store state
  -> Cofree f state
  -> Cofree f stateNote that this function will not dispatch subscriptions.  If you build that
into your interpreterer or you can use dispatchP which does not run
subscriptions (the P suffix stands for pure).
Redox.DSL.dispatchP
  :: forall state dsl eff
   . (Error -> Eff (redox :: REDOX | eff) Unit)
  -> (dsl -> state -> Aff (redox :: REDOX | eff) state)
  -> Store state
  -> dsl
  -> Eff (redox :: REDOX | eff) (Canceler (redox :: REDOX | eff))You can modify your interpreter using
Redox.Utils.hoistCofree'
  :: forall f state
   . (Functor f)
  => (f (Cofree f state) -> f (Cofree f state))
  -> Cofree f state
  -> Cofree f stateThis is a version of Control.Comonad.Cofree.hoistCofree but here the first
argument does not need to be a natural transformation.  This let you add
effects to the interpreter.  For example mkIncInterp is build using it.
Another example is to add a logger.
addLogger
  :: forall state f
   . (Functor f)
  => Cofree f state
  -> Cofree f state
addLogger interp = hoistCofree' nat interp
  where
    nat :: f (Cofree f state) -> f (Cofree f state)
    nat fa = g <$> fa
    g :: Cofree f state -> Cofree f state
    g cof = unsafePerformEff do
      -- Control.Comonad.Cofree.head 
      log $ unsafeCoerce (head cof)
      pure cofThere are plenty of other things you can do with the interpreter in this way, e.g. undo/redo stack, optimistic updates, crash reporting, delay actions (or just some actions, via prisms).