12

The strict state monad is defined using:

m >>= k = State $ \s ->
  case runState m s of
    (a, s') -> runState (k a) s'

But this can still leak memory, because a and s' are left unevaluated. For example, we might have a function f that takes a large object as input and quickly returns (a, s'), but as long as a is left unevaluated the input to f cannot be GC'ed.

One potential solution is to have f return seq a (a, s'), but this isn't always possible if we are using something like MonadRandom, and the state is encapsulated away from f. Is there a version that is defined like this:

m >>= k = State $ \s ->
  case runState m s of
    (!a, !s') -> runState (k a) s'

Does this exist in a library anywhere already?

dfeuer
  • 48,079
  • 5
  • 63
  • 167
yong
  • 3,583
  • 16
  • 32
  • 2
    @GabrielGonzalez has done some work on making stricter transformers, such as this [more strict WriterT](https://www.haskell.org/pipermail/libraries/2013-March/019528.html). – Cirdec Jan 18 '15 at 19:03
  • 7
    I would be tempted to make this strict only on `s`. If a strict `a` is wanted, the caller can simply `m >>= \ !a -> ...` to achieve the same effect, I think. – chi Jan 18 '15 at 19:18
  • 2
    Making it strict in `a` will violate the monad laws, so you don't want to do that. When making it strict in `s`, you will also have to change `return` to `return x = State $ \ !s -> (x, s)` or however it goes to preserve the identity laws. – dfeuer Jan 18 '15 at 21:27
  • Try it and see. You'll likely break the monad laws (and introduce e.g. immediate failures). – Don Stewart Jan 19 '15 at 17:43
  • @dfeuer I think you could turn your comment into an answer, to close this out as an unanswered question? – sclv Feb 20 '15 at 04:04

1 Answers1

6

According to the monad identity laws,

return a >>= const b = const b a = b

Thus in particular,

return undefined >>= const b = b

If the >>= operation were strict in the result value, that would break this law, so you shouldn't do that.

Suppose you instead do this:

m >>= k = State $ \s ->
  case runState m s of
    (a, !s') -> runState (k a) s'

Now we face another identity law:

m >>= return = m

For example,

return a >>= return = return a

So if return a >>= return is strict in the state, then we must also have return a strict in the state! So we need to redefine return as well:

return a = State $ \ !s -> (a, s)

Note that you don't really need to do any of this; if you want, you can use the usual strict state monad, and write things like

!_ <- get

in the spots where you want to force the state. You could even write an action to do this:

forceState :: Monad m => StateT s m ()
forceState = get >>= \ !_ -> return ()

Edit

Even this definition feels a little bit strange to me; I would expect the lambda to force the state, rather than the case. I'm not sure if not doing that leads to some kind of breakage, but it wouldn't surprise me if it did.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • 3
    In the presence of `seq` all monads fail the laws (also lazy `Writer` and `State`), see [this answer](http://stackoverflow.com/a/12620418/1333025). – Petr Feb 20 '15 at 09:28
  • 1
    @PetrPudlák, yes, that is a sad thing. We should still do what we can when reasonable, right? – dfeuer Feb 20 '15 at 14:43
  • 2
    @PetrPudlák, in particular, I think it's best to make sure your `Monad` instances obey the laws *as long as no one using them does stupid `seq` tricks*. – dfeuer Feb 20 '15 at 14:47
  • 2
    Stupid `seq` tricks will be extra nasty in base version 4.8, because `Identity` will be taking full advantage of people not doing that, with definitions like `fmap=coerce`, `foldMap=coerce`, `foldl=coerce`, and `foldl'=coerce`. – dfeuer Feb 20 '15 at 14:53
  • 1
    I wish that Haskell incorporated proper inductive and coinductive data-types. Then there would be no problems with `seq`/`undefined` for such data types. – Petr Feb 20 '15 at 14:58
  • 1
    @PetrPudlák, do you have any links to explain what you mean by any of that? – dfeuer Feb 21 '15 at 14:53
  • 2
    See [coinduction](https://en.wikipedia.org/wiki/Coinduction), [corecursion](https://en.wikipedia.org/wiki/Corecursion) and also [Total functional programming](https://en.wikipedia.org/wiki/Total_functional_programming). It's also worthwhile to read linked [the paper](http://www.jucs.org/jucs_10_7/total_functional_programming). In particular, inductive data types are always finite and have no bottoms, coinductive data types (codata) can me infinite, but again no bottoms. And what's really nice that this can be enforced syntactically. So `seq` can only alter performance, not the result. – Petr Feb 21 '15 at 17:47
  • @PetrPudlák, I read some of that paper; it looks like its author is living in some other universe in which programmers want all their programs verified total (so they are willing to give up Turing completeness) and will even settle for a much weaker version of termination checking than is available in Coq or Agda (so many useful programs are ridiculously hard to write, or impossible). – dfeuer Feb 24 '15 at 17:30
  • 1
    I believe the paper has many worthy ideas. Sometimes it's too difficult to make a whole program total, but it's good to aim at least for its parts. It's easier (and also enforced) in Agda or Coq, but there other things get harder. And giving up Turing completeness isn't that bad in many cases, in particular in the presence of codata, when it's possible to have infinite data structures and potentially infinitely running programs. – Petr Feb 24 '15 at 19:44
  • @PetrPudlák, I hear you, but twisting one's brain (and program) into knots to *attempt* to cram it into something like Turner's system does not sound very appealing to me. I agree that making programs as obviously total as reasonable is a good objective, as is getting help from the compiler to prove totality when reasonable. – dfeuer Feb 24 '15 at 19:53
  • 1
    I agree. My approach is somewhat similar to `IO`/pure code. I try to keep non-total code separated from total, even if it's just the matter of separating it into functions. This approach makes me more aware of what is potentially problematic (non-total) and focus a bit more on reasoning about its correctness. – Petr Feb 24 '15 at 20:17