Composing Elm programs

264 views
Skip to first unread message

Brian Slesinsky

unread,
May 3, 2014, 8:31:42 PM5/3/14
to elm-d...@googlegroups.com
Large programs tend to have multiple screens. For example, a Mario game might have a starting screen, a help screen, a settings screen, the game itself, and cut scenes. This is also true of presentations (each slide is a screen) wizard-style interfaces, and mobile phone apps.

Elm seems nice for writing a single screen, but not so great when you have multiple screens; you can't easily take a bunch of Elm programs and put them all together into a slideshow within Elm itself. The easiest way would be to do this outside Elm, either by putting programs on different HTML pages or by using JavaScript.

It might be possible to write a purely-functional framework for your slides (perhaps based on Automatons), but if you didn't have that in mind when you wrote each subprogram, you're faced with a big rewrite. Only the top-level program can use signals directly; the subprograms would have to be written some other way.

So maybe there should be a way to wrap up an Elm program into a command? Suppose we had something like this:

-- A scene is a command that runs to completion and outputs a value.
type Scene out

-- An external Elm program can be imported as a function from an input to a Scene.
-- For example:
myDialog : String -> Scene String
myDialog = importScene "path/to/dialog.elm"

-- A scene that does nothing but return a value
emptyScene : a -> Scene a 

-- You can decide to play another Scene after the first one finishes
bindScenes : Scene a -> (a -> Scene b) -> Scene b

-- The main value of an Elm program can play a signal until it exits with a value, causing the
-- program to exit.  The SceneFrame type allows the program to exit:
data SceneFrame a = Frame Element | Exit a

-- For example, the main value of a dialog might be:
main: String -> Signal (SceneFrame String)

-- Alternately, the main value can be a function from some input to a Scene that should be run:
-- For example, the main method of the overall program might be:
main : String -> Scene String

This avoids monadic FRP by requiring each scene to be a separate Elm program. It might be nice to construct multiple scenes in the same Elm file, but I don't see a good way to do it without radically changing how signals work.

Also, the top-level program only gains control between two scenes and they always take up the entire view. The effect is something like an operating system that doesn't support multi-tasking. This isn't so flexible, but if it's good enough for the iPhone, maybe it's not so bad?

- Brian

Brian Slesinsky

unread,
May 3, 2014, 8:38:27 PM5/3/14
to elm-d...@googlegroups.com


On Saturday, May 3, 2014 5:31:42 PM UTC-7, Brian Slesinsky wrote:

-- Alternately, the main value can be a function from some input to a Scene that should be run:
-- For example, the main method of the overall program might be:
main : String -> Scene String


Oops, the main program should probably be just a Scene, since it doesn't take an input.

- Brian

Alex Neslusan

unread,
May 3, 2014, 9:03:19 PM5/3/14
to elm-d...@googlegroups.com
I've been successfully composing Elm programs using Tim's InputGroups library along with a signal keeping track of which screen I'm currently on. It works well and doesn't need much boilerplate.

Here's a gist showing what I've been doing: https://gist.github.com/deadfoxygrandpa/6cb2362eee4d3ba112f9

In that, Program and Menu could be self-contained Elm programs that can be run completely independently. With this approach, it can be easily extended to even more independent programs. Once you have the basic structure I've shown set up, adding an additional program is just a matter of adding tracking to the State, adding an InputGroup for it, then adding 1 more line of logic to the main function.
Message has been deleted

Max Goldstein

unread,
May 3, 2014, 9:25:47 PM5/3/14
to elm-d...@googlegroups.com
Brian, I think that's a really neat idea and a useful example with which to think about commands. Having tried to do multiple scenes in pygame a few years ago, it's a worthwhile and tricky problem. I like the idea of scenes having interfaces between each other defined by the types they pass from one to the next.

I have a counterproposal to knowing when a scene is over. Leave main alone, and have a separate Signal (Maybe a), and when a Just occurs, the scene is over. I think each scene should belong in its own module, which would be responsible for exporting a record or tuple of the main and end functions, which is used to create a Scene. How we freeze that signal graph so it's not active until the scene plays will be tricky. It reminds me of Automatons being able to switch among a finite number of static signal graphs, so I think it's theoretically possible.

Here's my best attempt to punch holes in the idea. Let's say I have a game scene that ends in either a win screen or a lose screen. The game could put this information into its a and the bound function decides whether you won or lost and provides the next scene accordingly - so long as both of these scenes return the same type. What if I want to branch into one of many different Scene types? Should I be worried about the case analysis in the bound function exploding as paths diverge?

Brian Slesinsky

unread,
May 3, 2014, 9:56:19 PM5/3/14
to elm-d...@googlegroups.com

On Saturday, May 3, 2014 6:25:47 PM UTC-7, Max Goldstein wrote:

I have a counterproposal to knowing when a scene is over. Leave main alone, and have a separate Signal (Maybe a), and when a Just occurs, the scene is over.

Makes sense. I suppose you could have two special top-level values, something like:

main = ...
mainExit = ..

Or alternately, main could be assigned a pair of signals.

- Brian 

Brian Slesinsky

unread,
May 3, 2014, 10:07:45 PM5/3/14
to elm-d...@googlegroups.com


On Saturday, May 3, 2014 6:25:47 PM UTC-7, Max Goldstein wrote:
 
Here's my best attempt to punch holes in the idea. Let's say I have a game scene that ends in either a win screen or a lose screen. The game could put this information into its a and the bound function decides whether you won or lost and provides the next scene accordingly - so long as both of these scenes return the same type. What if I want to branch into one of many different Scene types? Should I be worried about the case analysis in the bound function exploding as paths diverge?

I don't quite understand what you mean, but it seems like if it were a problem, it would also come up when using the IO type in Haskell? (I'm trying to avoid saying "monad", but take a look at the emptyScene and bindScenes functions. :-)

Incidentally, I think I screwed up a bit on the naming because "Scene" makes sense for leaf nodes but not for composites. In drama, scenes aren't composed out of other scenes. :-)

Max Goldstein

unread,
May 3, 2014, 10:24:40 PM5/3/14
to elm-d...@googlegroups.com
The idea is to avoid changing the type of main, so that the scene module can be run independently. (I just checked, running a module with main still works.) Being able to debug level n of a game without playing levels 1 .. n-1 is a huge benefit. (Or jump to an arbitrary Scene, more generally.)

I think we may need to have the traditional Elm brainstorm over mainExit, but it's on the right track. There is also the alternative of the top-level Scene binder to specify the exit signal. Pros: can exit a scene on different criteria, like game difficulty, and exit with different types. Cons: module no longer controls the criteria for its exit.

The reciprocal question is, where does input from the previous scene come from? How can it be "stubbed out" for testing, without needing more language infrastructure like parametric modules?

I don't quite understand what you mean
So let's say I have a Scene Bool, and depending on how it exits I want to choose one of two different scenes to comes next. The bound function can be as simple as \b -> if b then sceneTrue else sceneFalse. This works only if sceneTrue and sceneFalse have the same type, but they may return radically different things. In a UI, you may be on very different screens based on prior clicks, and each will want an exit type specialized for it. I'm not in a position to compare this to monadic IO.

If you don't like Scene, maybe Story?

Evan Czaplicki

unread,
May 3, 2014, 10:24:50 PM5/3/14
to elm-d...@googlegroups.com
I have not read this carefully, but I'd look into Haskell's pipes library and Arrowized FRP before going too far with this kind of idea. Pipes I believe has a concept of "I'm done" and can be chained, so you then move on to the next thing. Arrowized FRP is all about how to swap things in and out dynamically.


--
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/d/optout.

Brian Slesinsky

unread,
May 3, 2014, 10:36:20 PM5/3/14
to elm-d...@googlegroups.com

On Saturday, May 3, 2014 6:03:19 PM UTC-7, Alex Neslusan wrote:
Once you have the basic structure I've shown set up, adding an additional program is just a matter of adding tracking to the State, adding an InputGroup for it, then adding 1 more line of logic to the main function.

InputGroups look interesting but they don't seem to isolate subprograms all that well? It seems like we're missing lifecycle events, so we know when each subprogram should start to run. The subprogram should decide for itself when it wants to exit (although perhaps the parent program can suspend it). If a subprogram is started twice, it seems like it shouldn't have any state left over from the last time it ran unless it's explicitly passed in.

It might be nice if it were easy to map it to the API's used on mobile phones, such as Activities and Intents on Android.

- Brian

Alex Neslusan

unread,
May 4, 2014, 5:53:35 AM5/4/14
to elm-d...@googlegroups.com
I guess you can't have a program signal when it's done, you need to keep track of that outside the subprogram. I think once Signal Loops are added, this should be possible though.

Jeff Smits

unread,
May 4, 2014, 8:10:22 AM5/4/14
to elm-discuss
I agree, with signal loops this should all be possible within Elm.
(BTW: I have resumed work on the idea to have it integrated in Elm at some point. )

Brian Slesinsky

unread,
May 4, 2014, 12:39:49 PM5/4/14
to elm-d...@googlegroups.com
Producers in Pipes use the return value of the underlying monad to signal "I'm done" and chaining seems to be done using the bind operation from the underlying monad. The "next" function uses Either to return the next value or "done". I suppose the equivalent would be to build on top of whichever effects abstraction Evan chooses for Elm.

Importing an external Elm program as an Automaton is an interesting idea; the subprogram would be represented as a state machine and this would allow the parent program to step though it with canned input like when using a debugger. This might be a nice way to write high-level tests for an Elm program, or to feed it input that's been transformed in some way. But it seems like the input type for each step might be complicated, since it has to cover all the input signals that the subprogram might receive.

Using an Automaton would also allow the parent program run multiple subprograms at the same time in subviews, sort of like using iframes in HTML.

On the other hand, if we import the subprogram as a command and run it (suspending the parent program), it can take its input from the original signals. For example, mouse input comes from the actual mouse for the duration of the subprogram. This is an easier but less flexible way to run a subprogram; perhaps it would be best to have both?

Another possibility might be to override some of the subprogram's input signals but not others. This would be similar to redirecting a file descriptor to alternate input when running a Unix command. A subprogram might also declare an input signal that's not hooked up to anything and the parent program would supply it.

- Brian

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/Hdpn3x0554c/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

John Nilsson

unread,
May 4, 2014, 12:52:46 PM5/4/14
to elm-d...@googlegroups.com
Angulars ui-router is an interesting approach to scene selection part of the problem https://github.com/angular-ui/ui-router

When trying to implement signals in a recent .Net WPF project using Rx I ended up composing  them in a rather imperative way that I guess resembles the signal and slot approach in Qt http://qt-project.org/doc/qt-4.8/signalsandslots.html 
It gets rather tricky to make sure everything is connected and disconnected at the right time, but maybe this gets easier with support from the runtime, or at least with the HSM approach used by the ui-router project above.

In my case I was aiming for keeping track of two HSMs, a Session State for navigating the UI and a Persistent State for, well persistent state. Commands operating on the Persistent State would thus be simple functions, like transactions in datomic ( http://docs.datomic.com/transactions.html ) with results then propagated back through the signals originating from this state.

How do you model that in Elm? It seems to me it doesn't make much sense in having an output signal with the last command. While a signal with a command stack of all commands seems unnecessary, unless that stack structure is backed by a database rather than an in memory representation. I guess one could borrow the versioning approach from CQRS such that the command knows which version of the state it should be applied to, then a signal of a queue of not-yet-applied commands would be reasonable.


BR,
John




Evan Czaplicki

unread,
May 4, 2014, 12:54:51 PM5/4/14
to elm-d...@googlegroups.com
One way to think of this is as follows: Any Elm program is a component with some incoming ports and some outgoing ports. Right now, you can only talk about that from JavaScript and there are some ports that are implicit.

If you can make all ports explicit and have a way to talk about them in Elm, you've got everything encapsulated in a way that can be paused easily. Swapping things in and out is a matter of connecting and detaching ports.

I think this is the route we should try to take. In this world, it is not clear if we can keep a component pure (as an Automaton would be) and efficient. So if it can't be both, then we probably need to start talking about how to handle effects. For now, I'd think about what this world would look like and what would be necessary, not about particular APIs yet.

John Nilsson

unread,
May 4, 2014, 1:02:15 PM5/4/14
to elm-d...@googlegroups.com
This approach is more or less the FBP approach, as long as ports are bounded queues of messages. So something based on NoFlo components might make sense? http://noflojs.org/documentation/components/

BR,
John

Max Goldstein

unread,
May 4, 2014, 3:09:08 PM5/4/14
to elm-d...@googlegroups.com
A module already has outgoing "ports" by means of what it exports. Now we just need imports, with defaults in case it's not called from a larger Scene.

"connecting and detaching ports" sounds like the forbidden dynamic signal graph and John's description of QT's signals and slots. I think the heart of this proposal is switching among static signal graphics, with results chained to input. An entire module becomes an automaton, with hidden state, main output, and exit (aux?) output. It can be paused an rewound as easily as any Elm program. An entire module effectively becomes an a -> (Signal Element, Signal (Maybe b)) and is no less pure than anything else that uses signals.

My two cents are to add ports of sorts to the module system specifically. Rather than changing port wirings, just use Scene's monadic operations (better phrasing?) to activate only one module's signal graph at a time.

But as usual, it's possible I'm missing something.
Reply all
Reply to author
Forward
0 new messages