0

Looking at purescript-signal examples, I have seen that a common pattern is to use a pure update function and an effectful render function this way:

runSignal $ map render (foldp update initialState input)

But what to do if rendering needs to keep a state, e.g. because effectful rendering operations inside render return values that are needed by following calls to render?

The unwrap function looked to me like the way to go, with something like this:

unwrap $ foldp render (pure initialRenderState) (foldp update initialState input)

I made the following code to try out unwrap, but it did not work as I expected. I was expecting it to print subsequent integers starting from 0, with a delay of one second between them. Instead, every second it prints not only the new number, but also all the previous ones.

updateEff :: forall eff. Number -> Eff (console :: CONSOLE | eff) Int -> Eff (console :: CONSOLE | eff) Int
updateEff _ stateEff = do
    state <- stateEff
    log $ show state
    pure $ state + 1

main = do
    unwrap $ foldp updateEff (pure 0) (every 1000.0)

Again, this code is just an example to try out unwrap, I know that these particular operations could be rewritten e.g. using a pure update function and a "render" function: runSignal $ map render (foldp update 0 (every 1000.0))

Why is the whole sequence of numbers getting printed again every time?

cmant
  • 453
  • 1
  • 5
  • 11

1 Answers1

1

Why is the whole sequence of numbers getting printed again every time?

It seems that using foldp you are creating signal which builds up larger and larger Eff value. So for example after third iteration you have something like this in your aggregated effect:

state ← do
  state ← do
    state ← pure 0
    log $ show state
    pure $ state + 1
  log $ show state
  pure $ state + 1
log $ show state
pure $ state + 1

foldp doesn't run your Eff values. On the other hand unwrap according to the documentation "(...) takes each effect produced by the input signal and runs it (...)" so Eff values produced by your foldp signal are evaluated at the end after they are aggregated, so we are observing accumulated effects.

But what to do if rendering needs to keep a state, e.g. because effectful rendering operations inside render return values that are needed by following calls to render?

I'm not sure if this is the best approach but you can use mutable reference from purescript-refs to keep state between invocations - something like this should work:

module Main where

import Prelude

import Control.Monad.Eff.Console (logShow)
import Control.Monad.Eff.Ref (modifyRef, newRef, readRef)
import Signal (unwrap)
import Signal.Time (every)

main = do
  s ← newRef 0
  let
    updateEff _ = do
      readRef s >>= logShow
      modifyRef s (_ + 1)

  unwrap $ map updateEff $ every 1000.0
paluh
  • 2,171
  • 20
  • 14
  • Thank you, now I understand why unwrap works like that. As for using mutable data to keep state, I preferred there was a way to use pure data structures with foldp. However I cannot find a way to use foldp, so mutable data looks like the only option for now. – cmant Jan 02 '18 at 16:45