Request: Loop construct

173 views
Skip to first unread message

Jeff Smits

unread,
Apr 21, 2013, 3:29:15 PM4/21/13
to elm-d...@googlegroups.com
This is my first official feature request ^_^. I've been thinking about this for a long time*, and I think I've more or less perfected the idea. Because of the long "development" time, this is going to be a long post. I'm trying to be as complete as I can about it, and I have some code examples and diagrams. I do expect the reader to be familiar with Evans thesis [1], which explains about the implementation of the runtime system with synchronisation using Change/NoChange queues. 

* This idea is the product of reading Evans thesis, having some sleepless nights, making chaotic notes on more and more paper, thinking about this while I was supposed to be studying for some tests, writing a proposal only to discover it doesn't work... I guess what I'm trying to say is that this took some serious effort, so please be gentle with your critique :)

Feature Request: Loop construct
The general problem
Apparently we can work with signal graphs in ours minds, but the limitation of a DAG is not always easy to work with. (See background materials, loops have been discussed on this mailing list many times [2-6]). 

Why cycles are bad
The reason normal cycles are bad is because they get out of control. When a signal depends on itself then a Change of the value of that signal will make it recompute its value, and because of that recomputation it can fire another Change, and so on in an endless loop. This non-paced behaviour of the signal is not wanted, and should be avoided at all times, statically. So there are no loops in Elm. 

A safe cycle
But if you look carefully, there is actually a very constrained looping construct in Elm: foldp (Evan mentions this in [2]). Here is a diagram in the style of the thesis:
Inline image 1

So how is it that this construct is safe? Well if you were allowed to define signal dependant on themselves in Elm, then result = foldp f b s could be expressed with:
result = lift2 f s $ sampleOn s result
-- note the lack of b, because sampleOn doesn't need a base case (which I find odd)
I mean to illustrate with this code example that foldp makes the loop safe by not reacting on a change from that cyclic signal, but only considering the pace that input s has. 

The power of foldp
The loop diagram of foldp seems rather simple, and yet most of the time it is a construct which is powerful enough. This power really comes from the rest of Elm. First of all, Elm is a functional language, which means first-class functions, which means easily composable functions. For example:
Inline image 2
translates easily to foldp (f.g) b s
The second easy this in Elm is combining and splitting signals:
Inline image 3
These tranlate to foldp (uncurry f) b $ (,) <~ s1 ~ s2,
and (r1,r2) = let r = foldp (\s (_,b) -> f s b) b s in (fst <~ r, snd <~ r)
Note that that last one already becomes harder to write. When you try to do more difficult things within the loop, the code becomes more convoluted and less the way you see it in your head:
Inline image 5
(foldp (\(s1,s2) -> g s2 . f s1 ) b $ (,) <~ s1 ~ s2)
Apart from the packing and unpacking of signal to get everything in the corset that is foldp (which can cause needless recomputation), there is something you really can't do within the foldp loop: filtering. Because a filter function works on with the full Event type, not just the value of the Signal. Apart from being unable to write pure functions over the Event type because Elm doesn't expose it to the user, you would not be able compose these function with pure value functions. 

Concluding
Cycles are bad because they spin out of control. foldp introduces safe cycles by pacing the cycle with an external signal (ignoring changes from the cyclic part). Though the loop/combine function is pure and works on one external input and the cyclic signal, functions can be composed and signals can be combined and split. But: 
  • A complicated signal graph with a loop doesn't map in a straight-forward way to code with foldp for the loop. (debatable, depending on coding style)
  • All the packing and unpacking (combining/splitting) of signals can also cause needless recomputation. 
  • No filtering can be done inside the loop. 
Specific use-cases
This is a situation I really ran into while writing Extreme Pong [2]. 

You want to write a little browser-game in Elm. The game has some game logic in the input and the state of the game, and shows some animations. Because it shows animations, fps is a handy part of the input. 

input = Input <~ (fps 60) ~ otherKeyboardStuff
gameState = foldp gameLogic stdGameState input
main = render <~ gameState

Now consider adding a feature to pause the game. If a keyboard key is pressed, the game state turns to paused. Then you can ignore the fps signal for a bit because there is no animation required. To give the CPU of the gamer a break, you can use fpsWhen and have a Bool signal called playing which is changed by the keyboard key. All is well.

input = Input <~ (fpsWhen 60 playing) ~ otherKeyboardStuff
gameState = foldp gameLogic stdGameState input
main = render <~ gameState

Now consider that instead of giving the gamer the ability to pause the game, you give the game logic the ability to pause the game. Alas, now you cannot use fpsWhen anymore! Because playing is influenced by the foldp gameLogic, depends on it, and thus depends on input, and thus on fpsWhen, which depends on playing again. fpsWhen is a specialised filter on input signal fps. And filters are not allowed in foldp loops. So there is no solution, you just have to use the fps signal and ignore it when the game is paused. 

I don't know any other simple examples of things you want to express with a filter in a signal loop, but I expect that there are more. 

Specific approach
The solution I would like to propose is a construct --- let's call it loop for now --- more easily create a loop (or cycle) in Elm. This would be done in the easiest way by using a foldp-like function which has an extra argument, which is a signal which will be defined later and may thus be dependant on the signal being defined with loop:

Inline image 7
The use-case I presented could then use fpsWhen even when using gameLogic for pausing:

split s = (fst <~ s, snd <~ s)
newPlaying spacebar (gameLogicPause,oldPlaying) =
  if| gameLogicPause -> False
    | spacebar       -> True
    | otherwise      -> oldPlaying
playing = loop newPlaying False ((,) <~ pause ~ playing) Keyboard.space
input = Input <~ (fpsWhen 60 playing) ~ otherKeyboardStuff
(gameState,pause) = split $ foldp gameLogic stdGameState input
main = render <~ gameState

Background materials
List discussions:
[2] My question with use-case
[6] same explanation mentioned again by John

Alternative solutions
Right now this kind of functionality is possible using the FFI. Though the code style does make it very clear that something special is going on, but it's a (dangerous) hack. You may introduce unsafe loops this way. 

Another solution which would require a less special construct would be:
loop : (Signal a -> Signal b -> Signal b) -> b -> Signal a -> Signal b
This alternate doesn't promote a style in which you define pure functions only. I don't like that way, with some signals defined inside a function, it looks messy:
-- hidden state loop
hsLoop : (Signal i -> Signal s -> Signal (o,s)) -> (o,s) -> Signal i -> Signal o
hsLoop sf b s = lift fst $ loop (\i (_,s) -> sf i s) b s

split : Signal (a,b) -> (Signal a, Signal b)
split s = (fst <~ s, snd <~ s)

newPlaying : Bool -> Bool -> Bool -> Bool
newPlaying gameLogicPause spacebar oldPlaying =
  if| gameLogicPause -> False
    | spacebar       -> True
    | otherwise      -> oldPlaying
    
playingLF : Signal Bool -> Signal Bool -> Signal (GameState, Bool)
playingLF space playing = 
  let timeDelta = fpsWhen 60 playing

      input = Input <~ timeDelta ~ otherKeyboardStuff

      (gameState, pause) = split $ foldp gameLogic stdGameState input
  in (,) <~ gameState ~ (newPlaying <~ pause ~ space ~ playing)

gameState : Signal GameState
gameState = hsLoop playingLF False Keyboard.space

main : Signal Element
main = render <~ gameState

Known issues
Things to consider about the loop function as proposed:
  • Name/Argument order
  • It now looks like a normal function, but doesn't behave like one because one of the arguments is very special! Perhaps a language construct which looks distinctly different would be better to make sure you pay attention to these parts of the code? e.g.: r = loopBack sb with f b s
    On the other hand, other functions on signals are normal functions too... Perhaps do something with the type signature? e.g.: loop : (a -> b -> c) -> b -> FutureSignal b -> Signal a -> Signal c
  • Is it always the case that it is also handy to have the last value of the signal result? Then f : a -> c -> b -> c would be better...
  • Will this create more complex code? i.e. is it harder to read? (I find it easier to write, but is it easy to read for others?) Will it promote a messier coding style? 

Justification
I think the known issues need to considered carefully. But otherwise I think this is a safe and simple way to introduce loops in Elm, making it a more powerful language. It takes out all three parts of the problem: 
  • A complicated signal graph with a loop doesn't map in a straight-forward way to code with foldp for the loop. (debatable, depending on coding style)
  • All the packing and unpacking (combining/splitting) of signals can also cause needless recomputation. 
  • No filtering can be done inside the loop. 
I don't think it will be very complex to implement this in the runtime. I don't know how well the compiler checks on cycles now and how dependant it is on DAGs though. 

-- end of feature request
elm_fold.svg
elm_fold_multisign.svg
elm_fold_fg.svg
elm_fold_fg_multi.svg
elm_loop.svg

John Mayer (gmail)

unread,
Apr 21, 2013, 4:50:36 PM4/21/13
to elm-d...@googlegroups.com, elm-d...@googlegroups.com
Yay I was cited!

I think your explanation is spot on - foldp can be thought of as a loop via some safe external state.

Would your "loop" have an analogy in Automaton? I need to sit down with a larger screen ;-)

I agree with the basic assumptions that loops are useful and that they can be implemented relaxing or generalizing the "Signals are a DAG" specification.

Sent from my iPhone

On Apr 21, 2013, at 3:29 PM, Jeff Smits <jeff....@gmail.com> wrote:

This is my first official feature request ^_^. I've been thinking about this for a long time*, and I think I've more or less perfected the idea. Because of the long "development" time, this is going to be a long post. I'm trying to be as complete as I can about it, and I have some code examples and diagrams. I do expect the reader to be familiar with Evans thesis [1], which explains about the implementation of the runtime system with synchronisation using Change/NoChange queues. 

* This idea is the product of reading Evans thesis, having some sleepless nights, making chaotic notes on more and more paper, thinking about this while I was supposed to be studying for some tests, writing a proposal only to discover it doesn't work... I guess what I'm trying to say is that this took some serious effort, so please be gentle with your critique :)

Feature Request: Loop construct
The general problem
Apparently we can work with signal graphs in ours minds, but the limitation of a DAG is not always easy to work with. (See background materials, loops have been discussed on this mailing list many times [2-6]). 

Why cycles are bad
The reason normal cycles are bad is because they get out of control. When a signal depends on itself then a Change of the value of that signal will make it recompute its value, and because of that recomputation it can fire another Change, and so on in an endless loop. This non-paced behaviour of the signal is not wanted, and should be avoided at all times, statically. So there are no loops in Elm. 

A safe cycle
But if you look carefully, there is actually a very constrained looping construct in Elm: foldp (Evan mentions this in [2]). Here is a diagram in the style of the thesis:
<elm_fold.svg>

So how is it that this construct is safe? Well if you were allowed to define signal dependant on themselves in Elm, then result = foldp f b s could be expressed with:
result = lift2 f s $ sampleOn s result
-- note the lack of b, because sampleOn doesn't need a base case (which I find odd)
I mean to illustrate with this code example that foldp makes the loop safe by not reacting on a change from that cyclic signal, but only considering the pace that input s has. 

The power of foldp
The loop diagram of foldp seems rather simple, and yet most of the time it is a construct which is powerful enough. This power really comes from the rest of Elm. First of all, Elm is a functional language, which means first-class functions, which means easily composable functions. For example:
<elm_fold_fg.svg>
translates easily to foldp (f.g) b s
The second easy this in Elm is combining and splitting signals:
<elm_fold_multisign.svg>
These tranlate to foldp (uncurry f) b $ (,) <~ s1 ~ s2,
and (r1,r2) = let r = foldp (\s (_,b) -> f s b) b s in (fst <~ r, snd <~ r)
Note that that last one already becomes harder to write. When you try to do more difficult things within the loop, the code becomes more convoluted and less the way you see it in your head:
<elm_fold_fg_multi.svg>
(foldp (\(s1,s2) -> g s2 . f s1 ) b $ (,) <~ s1 ~ s2)
Apart from the packing and unpacking of signal to get everything in the corset that is foldp (which can cause needless recomputation), there is something you really can't do within the foldp loop: filtering. Because a filter function works on with the full Event type, not just the value of the Signal. Apart from being unable to write pure functions over the Event type because Elm doesn't expose it to the user, you would not be able compose these function with pure value functions. 

Concluding
Cycles are bad because they spin out of control. foldp introduces safe cycles by pacing the cycle with an external signal (ignoring changes from the cyclic part). Though the loop/combine function is pure and works on one external input and the cyclic signal, functions can be composed and signals can be combined and split. But: 
  • A complicated signal graph with a loop doesn't map in a straight-forward way to code with foldp for the loop. (debatable, depending on coding style)
  • All the packing and unpacking (combining/splitting) of signals can also cause needless recomputation. 
  • No filtering can be done inside the loop. 
Specific use-cases
This is a situation I really ran into while writing Extreme Pong [2]. 

You want to write a little browser-game in Elm. The game has some game logic in the input and the state of the game, and shows some animations. Because it shows animations, fps is a handy part of the input. 

input = Input <~ (fps 60) ~ otherKeyboardStuff
gameState = foldp gameLogic stdGameState input
main = render <~ gameState

Now consider adding a feature to pause the game. If a keyboard key is pressed, the game state turns to paused. Then you can ignore the fps signal for a bit because there is no animation required. To give the CPU of the gamer a break, you can use fpsWhen and have a Bool signal called playing which is changed by the keyboard key. All is well.

input = Input <~ (fpsWhen 60 playing) ~ otherKeyboardStuff
gameState = foldp gameLogic stdGameState input
main = render <~ gameState

Now consider that instead of giving the gamer the ability to pause the game, you give the game logic the ability to pause the game. Alas, now you cannot use fpsWhen anymore! Because playing is influenced by the foldp gameLogic, depends on it, and thus depends on input, and thus on fpsWhen, which depends on playing again. fpsWhen is a specialised filter on input signal fps. And filters are not allowed in foldp loops. So there is no solution, you just have to use the fps signal and ignore it when the game is paused. 

I don't know any other simple examples of things you want to express with a filter in a signal loop, but I expect that there are more. 

Specific approach
The solution I would like to propose is a construct --- let's call it loop for now --- more easily create a loop (or cycle) in Elm. This would be done in the easiest way by using a foldp-like function which has an extra argument, which is a signal which will be defined later and may thus be dependant on the signal being defined with loop:

<elm_loop.svg>

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Ray Wach

unread,
Apr 22, 2013, 4:24:13 PM4/22/13
to elm-d...@googlegroups.com
I'm not literate enough on Elm to really remark on the merits of your proposal, but I definitely think that a safe signal looping construct should be in the language in some form. I understand how this is sort of already the case with foldp, but as Jeff put well, this is not ideal.

I think it makes sense to maintain some level of symmetry with foldp by making it a function that belongs to the same module as foldp.

Evan Czaplicki

unread,
Apr 22, 2013, 5:54:04 PM4/22/13
to elm-d...@googlegroups.com
Jeff, this is really cool! I definitely agree that this would be a great thing to have, and the specific uses are compelling.

I do not fully understand how the proposed loop function would work though. It looks like it requires recursively defined signals?

playing = loop newPlaying False ((,) <~ pause ~ playing) Keyboard.space

I don't understand what this means. Using normal evaluation rules, I think `playing` would just expand out infinitely? Examples:

sig = foldp f b sig
xs = 1 :: xs

Is loop special in some way here? Once I understand, I think I'll have more questions :)

There have been some FRP systems that have recursive signals (e.g. Real-Time FRP), but I am not an expert on how that would work with a discrete version of FRP.

Again, very cool work! I am going to be super busy for the next week, but it would be cool to talk about this idea on the phone or in video if that is okay with you. I think that will help me a lot.


On Mon, Apr 22, 2013 at 1:24 PM, Ray Wach <ure...@gmail.com> wrote:
I'm not literate enough on Elm to really remark on the merits of your proposal, but I definitely think that a safe signal looping construct should be in the language in some form. I understand how this is sort of already the case with foldp, but as Jeff put well, this is not ideal.

I think it makes sense to maintain some level of symmetry with foldp by making it a function that belongs to the same module as foldp.

--

Jeff Smits

unread,
Apr 23, 2013, 2:17:34 PM4/23/13
to elm-d...@googlegroups.com
So I'm pretty busy too, that's why I'm only responding now. 
You pointed out one of the problems I tried to explain in the known issues Evan. Particularly bullet point 2. 
The loop construct is not defining an actual recursive signal, but the third argument is allowed to be the name of a signal which is not defined yet. 

loop : (a -> b -> c)  -- merger function like foldp
    -> b              -- default value of the input signal
    -> Signal b       -- name of the input signal, which may be the output signal or a signal defined later which is dependant on the input signal. This is the funky part of the loop construct and that's why I mentioned the type FutureSignal as a possibly way to emphasize the funkiness. Read the Known Issues section of my original post for more possible solutions. 
    -> Signal a       -- input signal which paces the output signal, same as with foldp
    -> Signal c       -- output signal

Do you know understand why the following holds?
r = fold f b s
-- IS EQUIVALENT TO --
r = loop f b r s

If it's not clear we can talk about it on the phone or in video, but it will probably have to in the weekend because I'm really busy during the week with school and I don't think evenings on my end match with any kind of daytime on your end...

Jeff Smits

unread,
Apr 23, 2013, 2:19:23 PM4/23/13
to elm-d...@googlegroups.com
oops, let me amend something there:

loop : (a -> b -> c)  -- merger function like foldp
    -> b              -- default value of the loopback input signal
    -> Signal b       -- name of the input signal, which may be the output signal or a signal defined later which is dependant on the input signal. This is the funky part of the loop construct and that's why I mentioned the type FutureSignal as a possibly way to emphasize the funkiness. Read the Known Issues section of my original post for more possible solutions. 
    -> Signal a       -- input signal which paces the output signal, same as with foldp
    -> Signal c       -- output signal

Evan Czaplicki

unread,
Apr 23, 2013, 4:15:16 PM4/23/13
to elm-d...@googlegroups.com
Ohhh, okay! Yes, everything makes a lot more sense now! Sorry I missed it the first time around!

Is it true to say the following? `loop` allows arbitrary cycles without any risk of infinite feedback loops. I am trying to think if any kinds of loops would get ruled out or where this would be uncomfortable to use, but it seems really solid so far.

I'd be more inclined towards a special syntax for it. It feels more like a control flow operator like `if` or `case` on some level because it has a special order of evaluation.

I still need to give this more thought, but I am really excited about this proposal!

John Mayer (gmail)

unread,
Apr 23, 2013, 5:20:41 PM4/23/13
to elm-d...@googlegroups.com
Personally I think the following is slightly more elegant, and equivalent in expressiveness. The basic premise remains, but it scraps the "future signal" concept and doesn't add anything to the type system.

loop : (b -> a) -> b -> (Signal a -> Signal b) -> Signal b

The third argument is some signal transformer, around which we would like to build a cycle. The second argument is the default output value. You asynchronously loop the output back to the input, and you convert the output into the input using the first argument.

Also, note the usefulness of 

loop id : a -> (Signal a -> Signal a) -> Signal a

Thoughts? I just think it's a bit simpler, and cleaner because the guts of the loop are "closed" in a sense.

John

Sent from my iPhone

Evan Czaplicki

unread,
Apr 23, 2013, 5:28:59 PM4/23/13
to elm-d...@googlegroups.com
John, how does this let you "close the loop"? Can you do an example with `fpsWhen`?

John Mayer (gmail)

unread,
Apr 23, 2013, 5:49:47 PM4/23/13
to elm-d...@googlegroups.com
I admit I need to draw this. Look for it tonight. I'll do a "pauseable analog clock" 

Sent from my iPhone

John Mayer

unread,
Apr 23, 2013, 8:02:47 PM4/23/13
to elm-d...@googlegroups.com
--
/*
 * John P Mayer, Jr
 * BS, Computer and Information Science, 2011
 * MSE, Embedded Systems, 2012
 * School of Engineering and Applied Science
 * The University of Pennsylvania
 * Philadelphia, PA
 */

Evan Czaplicki

unread,
Apr 23, 2013, 8:08:51 PM4/23/13
to elm-d...@googlegroups.com
What is it supposed to do? I don't see how something that depends on fpsWhen can turn fpsWhen on and off.

John Mayer

unread,
Apr 23, 2013, 8:10:28 PM4/23/13
to elm-d...@googlegroups.com
There is a checkbox and a clock. When the checkbox is checked, the clock hands move.

Evan Czaplicki

unread,
Apr 23, 2013, 8:13:16 PM4/23/13
to elm-d...@googlegroups.com
Can you make it fpsWhen pause when it is 3pm? If I understand correctly, that is what Jeff is after.

John Mayer

unread,
Apr 23, 2013, 8:29:09 PM4/23/13
to elm-d...@googlegroups.com
Yes sure. I've updated the Gist. Revision 3 is the most current as of this message.

Jake Verbaten

unread,
Apr 23, 2013, 8:39:08 PM4/23/13
to elm-d...@googlegroups.com
I really like the idea of having more powerful loops.

Totally excited by all the ideas here!

Having a bit of trouble reading these examples, probably because I can't quite read that much haskell yet. Having a bunch of executable / simpler examples would help. Maybe one that's similar to the existing clock example, that one was readable.

John Mayer

unread,
Apr 23, 2013, 8:46:17 PM4/23/13
to elm-d...@googlegroups.com
I've created a drawing here


The key is that the loop back is anonymous in addition to being asynchronous.

Evan Czaplicki

unread,
Apr 23, 2013, 8:48:42 PM4/23/13
to elm-d...@googlegroups.com
Sorry if this is obvious. What stops the infinite loop?

John Mayer

unread,
Apr 23, 2013, 9:07:10 PM4/23/13
to elm-d...@googlegroups.com
Nothing. Or in other words, user code. The signal transformer you pass in shouldn't update its output on every input change. In my code this is enforced by the use of sampleOn and fps.

I'm only tackling one of issues: defining cyclic Signals introduces an infinite loop while updating the signal graph. My loop construct allows you avoid impossible signal graphs but still get cyclic behavior.

Jeff's proposal relies on the fact that one of the signals must be defined prior to using loop. This is how he ensures that the output loop construct does not depend only on the "looped-back" signal. But, as he mentioned, that's going to introduce some kind of new language construct.

Personally, I'd rather add something clever to the runtime/stdlib than to the AST.

John

PS: My loop utility is pretty much isomorphic to my LatchedComponent concept. It reinforces the idea that it's not the signal graph that needs to be a DAG, but rather the graph of await dependencies.

John Mayer

unread,
Apr 23, 2013, 9:15:17 PM4/23/13
to elm-d...@googlegroups.com
Actually a really good compromise would be this:

loop : (a -> c -> b) -> Signal a -> (Signal b -> Signal c) -> Signal c

This takes a loopback-combining function, a "when" function, and an update function.

You're guaranteed no infinite loops, and you don't need a special language feature.

Jeff Smits

unread,
Apr 24, 2013, 4:58:26 AM4/24/13
to elm-d...@googlegroups.com, john.p....@gmail.com
Ok, I get that my feature request is a long post, but please read it completely =\

@Evan:
Is it true to say the following? `loop` allows arbitrary cycles without any risk of infinite feedback loops. I am trying to think if any kinds of loops would get ruled out or where this would be uncomfortable to use, but it seems really solid so far.
Yes, that is exactly the idea, and I'm pretty sure it's safe :)

I'd be more inclined towards a special syntax for it. It feels more like a control flow operator like `if` or `case` on some level because it has a special order of evaluation.
I was thinking in that direction too, which is why I wrote it as possible solution to known issue #2. The reason I didn't put it in the main proposal was because I only thought of it while writing the proposal, and after having the idea in my head for so long I really wanted to just send the proposal and not have to change some things again. I also thought it might be strange to introduce a new language construct which isn't general but particularly about Signals. But I guess Haskell did the same with Monads when they introduced the do notation. 
So I agree that it should be special syntax. 

@John:
I can use your first proposal (loop : (b -> a) -> b -> (Signal a -> Signal b) -> Signal b) in two unsafe ways. 
One is the following:
changeSpammer = loop id 1 (lift id)
This will create a "source" signal which is not registered at the Event Dispatcher and keeps generating new Change 1 messages. It is completely out of sync with the whole Change/NoChange message system which keeps the signal graph in sync. In my opinion this situation should never be expressible in Elm because it destabilises everything.
The second unsafe way is less problematic:
alwaysChanging = loop id 1 ((\_ b -> b) <~ constant 2)
This signal will not spin infinitely because it has to wait for a Change or a NoChange message from the constant signal. But it does ignore that message from the constant signal, making it send a Change 1 message every time an Event occurs in some input source of the full signal graph. Though this is interesting behaviour some special situations, I think we should not have control over things like this. It's too powerful and low-level. We don't require our own filter function because Evan has put every necessary primitive filter in Elm. 

The reason I started this post with "please read my full proposal" is really because of your last post John. You mention a loop function there which I thought of about a week ago. I was going to have that as my proposal until I tried giving some examples of its use and found out I didn't like its usage at all. But it's a feature which is just as powerful as my proposed feature, so I added it to the section Alternative Solutions

To summarise my reasons to prefer my proposal (even though it's clearest as a language construct):
  • It's a very easy way to create safe arbitrary loops. (possible with the alternative solution)
  • You can easily add more input signals in some part of the loop. (possible with the alternative solution)
  • You can easily add "output signals" which are dependant from the signal in the loop. (annoying with the alternative solution)
That last reason is very important to me. I don't have time to show to create diagrams of complicated situations now, but I'll see if I can draw something tonight to show you what is so annoying about it with the alternative solution. 

John Mayer

unread,
Apr 24, 2013, 8:56:28 AM4/24/13
to elm-d...@googlegroups.com
I humbly submit that I read your whole post, and also that my compromise is not equal to your alternative solution.

loop : (Signal a -> Signal b -> Signal b) -> b -> Signal a -> Signal b

In your example, I can pass the following as a first argument

broken : Signal a -> Signal b -> Signal b
broken _ b = b

This now causes our "unsafe infinite loop"; note how the same tactic cannot be used in the following, as the loop function now must wait for both Signals, thanks to the introduction of the pure combining function (your idea in the paper).

loop : (a -> c -> b) -> Signal a -> (Signal b -> Signal c) -> Signal c

I like this "flavor" of loop better because it forces you to think hard about what's happening in the loop.

John

PS: I do think your post was really terrific.

John Mayer

unread,
Apr 24, 2013, 9:36:52 AM4/24/13
to elm-d...@googlegroups.com
Ah, sorry; I forgot the initial value of the loopback.

loop : (a -> c -> b) -> Signal a -> c -> (Signal b -> Signal c) -> Signal c

Which, by the way, can be implemented using my unsafe, raw loop.

unsafeLoop : (c -> b) -> c -> (Signal b -> Signal c) -> Signal c

loop combine sampler initialLoopback transform = unsafeLoop id initialLoopback safe
  where safe = sampleOn sampler (\sc -> transform (combine <~ sampler ~ sc))

Which is to say that we don't want any loop, we want a loop with implicit sampling behavior.

Evan Czaplicki

unread,
Apr 24, 2013, 1:45:33 PM4/24/13
to elm-d...@googlegroups.com
John, please move your proposal to a separate thread and use the formal structure. I'd like to focus on Jeff's proposal in this thread.

Jeff, I apologize if my questions seem simple! The last email was an attempt to clarify the scope of the proposal and provide initial feedback (after reading through a couple times too!).

I think I only have one question left, which may be residual misunderstanding. This is from the initial proposal:

playing = loop newPlaying False ((,) <~ pause ~ playing) Keyboard.space

Is it true that `playing` (on the left-hand side of the definition) will only be updated when the user presses Keyboard.space? If so, does that mean that the values of `pause` and `playing` are not observed unless the user presses space? If this is the case, I think this may be too restrictive to be used to programatically update fpsWhen. Does this make sense?

Jeff Smits

unread,
Apr 24, 2013, 3:00:13 PM4/24/13
to elm-d...@googlegroups.com
I guess I was a bit irritated when I wrote my last post, sorry that blended into my writing. 
So I guess some things are not completely clear from my proposal :P Some I'll have to clear up in a separate thread about Johns solution.

Anyway, your question is a very good one! You're not misunderstanding anything, you've found a serious problem in my proposed feature!
The general sampleOn kind of behaviour may make sure only safe loops can occur, but you're right, this way is too restrictive and playing will only update on a new Change event from Keyboard.Space =\
The purpose of the sampleOn behaviour of is to make sure a Change event to the loop signal doesn't trigger another Change on it own. But it's too strong apparently.  Instead only a Change event caused by the pacing signal of the loop construct should be ignored. That was my assumption of how it would have worked anyway. But that's a LOT harder to implement I think... 
I think that if we want the kind of behaviour I was hoping for, that would require annotating all mergers of an normal signal with the looping signal so that you can make any Change event from a normal signal makes a Change event go round the loop to a maximum of one round. 
I really want to draws some diagrams of it but I don't have time right now :( I'll see what I can do tomorrow.
Does what I wrote make sense to you?

Evan Czaplicki

unread,
Apr 24, 2013, 4:45:02 PM4/24/13
to elm-d...@googlegroups.com
Jeff, no worries! I do exactly the same thing :) It is something about the internet and text-only communication.

I think understand the revised behavior. My worry would be that if you pace on an fpsWhen signal and the relevant update comes from stepping the state forward by the FPS, you'll end up in the same trouble.

I think the goal should be the following invariant:
A looped update cannot cause another looped update.

This would bound loops to being only one round, avoiding the risk of divergence (infinite looping :P). I'm not sure if the revised behavior provides exactly this invariant.

Perhaps limiting it to some finite number of rounds should be considered as well.


--

John Mayer

unread,
Apr 24, 2013, 11:43:56 PM4/24/13
to elm-d...@googlegroups.com
I don't have a separate proposal, just feedback to Jeff's main point and a potential solution using existing structures in Elm. I've tried to summarize my approach as an incremental improvement on Jeff's approach in this document.


I think it makes it clear why I feel that we really don't need to be thinking about annotating Signals with dependencies or something of that nature.

de...@dsouzaville.com

unread,
Apr 25, 2013, 1:34:36 PM4/25/13
to elm-d...@googlegroups.com, john.p....@gmail.com
I get an "access denied" for the links inside your document, perhaps you forgot to share them with the world?

Jeff Smits

unread,
Apr 25, 2013, 2:59:56 PM4/25/13
to elm-d...@googlegroups.com, john.p....@gmail.com
@John:
I'd like to read your document, but could you make sure all the diagrams are available first?

@Evan: 
The invariant you named is exactly what I was trying to describe. But I was in a hurry when I wrote that last post and writing down my thoughts in a clear way is time-consuming :\
I think I've found a different solution for the introduction of a loop which supports the invariant you named so concisely as "A looped update cannot cause another looped update". But I think the explanation would be best clarified with some diagrams. And I have to think the idea over to see if it's completely safe and nice to use. I'll have time to draw some diagrams in the weekend. Do you want me to start a new feature request thread with the new proposal or shall we continue here? Because the one I started this thread with is not actually implementable with the behaviour I was looking for. But the new proposal is related I guess...

John Mayer

unread,
Apr 25, 2013, 9:07:58 PM4/25/13
to elm-d...@googlegroups.com
Oh no! Yes they should all be viewable now, sorry!

Evan Czaplicki

unread,
Apr 26, 2013, 12:10:07 AM4/26/13
to elm-d...@googlegroups.com
I'd go for a new thread, this one is really long :)
--
Sent from Gmail Mobile
Reply all
Reply to author
Forward
0 new messages