1

Let's take a look at an example given in the hackage documentation for stateful

do
  smp <- start (stateful "" (:))
  res <- forM "olleh~" smp
  print res

the output is:

["","o","lo","llo","ello","hello"]

What it does is push one character after the next into the signal network, first 'o', then 'l', 'l' again, and so on. The state transformation function given to stateful ((:), aka cons) takes an element and a list of elements and returns the list with the single element prepended. (In Haskell a String is a list of Chars)

What happens is this

(:) 'o' "" -- gives "o", then
(:) 'l' "o" -- gives "lo", etc

This is all quite straightforward, but did you notice there is a '~' at the end of the input string? So why does it not give

["","o","lo","llo","ello","hello", "~hello"]
                                    ^^^^^^

?

Probably because it gives the initial value first, then the result of the first invocation of (:) second, etc. So if we push in six characters (including '~') we get out "" after five characters have been prepended. The string "~hello" is actually in there, but on sampling the signal we get the old state (right before it is discarded).

I expected the following two (contrived) examples to produce the same output, but they do not:

-- 1
do
  smp <- start $ do
    n <- input
    return (n*2)
  res <- forM [1,2,3] smp
  print res -- prints [2,4,6]

-- 2
do
  smp <- start $ stateful 0 (\n _  -> n*2) -- ignore state
  res <- forM [1,2,3] smp
  print res -- prints [0,2,4]

-- edit: 3
-- to make the trio complete, you can use transfer to do the same
-- thing as 1 above
-- there is of course no reason to actually do it this way
do
  smp <- start $ transfer undefined (\n _ _ -> n*2) (pure undefined)
  res <- forM [1,2,3] smp
  print res

So my questions are:

  • Why does it do that? Is it important enough we get the initial value that we just have to accept this delay?
  • Could an alternative function return the next value as soon as it is available? (In other words, could this alternative function return "hello" right after the 'h' was sent in, so the '~' could be removed?) The resulting signal would then never produce the initial value.

edit: After Alexander pointed me to transfer I came up with this:

do
  smp <- start (stateful' "" (:))
  res <- forM "olleh" smp -- no '~'!
  print res -- prints ["o","lo","llo","ello","hello"]

stateful' :: a -> (p -> a -> a) -> SignalGen p (Signal a)
stateful' s f = transfer s (\p _ a -> f p a) (pure undefined)

stateful' seems to be very roughly 25% slower than stateful on my machine.

Higemaru
  • 354
  • 4
  • 10

1 Answers1

1

Probably because it gives the initial value first, then the result of the first invocation of (:) second, etc. So if we push in six characters (including '~') we get out "" after five characters have been prepended. The string "~hello" is actually in there, but on sampling the signal we get the old state (right before it is discarded).

That's exactly how stateful works. Quoting the documentation:

The initial state is the first output, and every following output is calculated from the previous one and the value of the global parameter

That answers the first of your questions.

As for the second, yes, an alternative function could return the new state immediately. In fact, there's such a function already, called transfer:

The current input affects the current output, i.e. the initial state given in the first argument is considered to appear before the first output, and can never be observed.

(It also carries state, which you in your examples should just ignore.)

With this function, the "hello" example can be rewritten like this:

do
  smp <- start (transfer
                 ""
                 (\character _ state -> character:state)
                 (pure undefined))
  res <- forM "olleh" smp
  print res
  • Thanks! As you said, I already had the answer to some of my questions. I completely missed the transfer function, probably because the example given isn't quite as simple as the other ones. Stateful's example seems much more appropriate to be an example for transfer. On closer inspection I guess transfer can do a lot more than just be the stateful-variant I was looking for. – Higemaru May 01 '16 at 11:43
  • I just ran your code and got `Prelude.undefined`. It works using `(pure undefined)` like in my edit of the question. – Higemaru May 01 '16 at 12:22
  • Ah, right; it doesn't work with `undefined` because somewhere in the guts of the library, it expects to see a `Signal` value and sees `undefined`; while `pure undefined` works because it's an `undefined` packed into a `Signal`. Library needs the container, but never looks at the packed value. I'll update my post. Thanks, and sorry for the confusion! – Alexander Batischev May 01 '16 at 12:45