pselm / signals

Purescript implementation of Elm 0.16's signals modules
Other
91 stars 2 forks source link

Integrate various ReaderT / StateT superstructures into something like Elm.Runtime #3

Open rgrempel opened 8 years ago

rgrempel commented 8 years ago

In order to match the simplicity of some Elm APIs, I've been using ReaderT and StateT in places like Elm.Signal, Elm.Mouse and Elm.Keyboard.

The way in which this simplifies the API is that some things which would otherwise need to be parameters to multiple functions can be provided once (or, in some cases, generated once), and then consulted by multiple functions without an explicit parameter. (So, the ordinary reason for ReaderT and StateT, I think).

The other way in which this helps is by creating "singletons" for some effectful things that the Elm API re-uses. For instance, the Elm API often has a "base" signal, like Mouse.position, and then some signals mapped from it, like Mouse.x and Mouse.y. The way that the Elm API handles this is by generating all defined signals (if the module was imported at all) at run-time, and then pruning those which were not used (in the sense of not connected to something in the signal graph that generates effects). So, both Mouse.x and Mouse.y can depend on one Mouse.position, because the Mouse.position is actually generated.

It would (I think) be difficult to follow that exact strategy in Purescript (though perhaps not impossible) -- and, in any event, it would be interesting to experiment with an approach that only creates the signals that are desired (so that no pruning stage is needed). One way to do this would be to change the signature of something like Mouse.x to require a Signal MousePosition as a parameter. So, you'd manually create a Mouse.position and then supply that to Mouse.x. However, I'd like to change the Elm APIs as little as possible. So, the alternative is to provide a setupMouse function that takes a callback which runs within a ReaderT that has access to a Signal MousePosition. In this way, the original Elm API can be supported, at the cost of adding a function call to setup the callback. (This works particularly well in Elm.Keyboard, where the common base structures are repeatedly re-used).

I'm looking at Elm.Window now, and realizing that one of the things it needs is the "container" node (possibly the <body>, but not necessarily), which is also needed by Elm.Mouse. So, one really ought to integrate those things ... for instance, by having an Elm.Runtime module that has a ReaderT or StateT that tracks the container node. The idea being that you only need to specify the container node once.

In fact, this integrated superstructure might also be a good place to locate the signal graph, since if you need Elm.Mouse or Elm.Window, then you obviously also need Elm.Signal, so there would be no waste. (There would be little waste in any event in just setting up an empty signal graph). So, we might be able to entirely integrate the setup method from Elm.Signal into an Elm.Runtime. We'd probably still need a separate setupMouse and setupKeyboard function (for the common base signals -- we wouldn't want to generate those more than once, or when not needed), but at least we could integrate some things.

So, I should look at establishing an Elm.Runtime module at some point.

rgrempel commented 8 years ago

It occurs to me that the other way to deal with the common base signals would be to have a StateT with a Maybe slot for each base signal. Then, when one needs the base signal, you either just read it from the StateT or, if it's Nothing, you actually create it and store it there.

The potential advantage of this is that you'd only need one setup method with a callback ... you wouldn't need a separate setupMouse, setupKeyboard, setupWindow etc.

The disadvantage, I suppose, is that this integrated setup method would need to import a wide cross-section of stuff. However, it would probably only need to actually use the types. And, in fact, to avoid circular imports, I'd probably need to specify the types in separate modules. So, it probably wouldn't draw unnecessary code into the build. (And, we do have dead-code elimination in any event, after all).

In any event, it's an option worth exploring.

rgrempel commented 8 years ago

Yet another way of structuring this (possibly) would be via Lazy, rather than Maybe ... that is, I think the Lazy structure is another way of saying "do this only once, if ever needed, and then keep giving the same answer".

rgrempel commented 6 years ago

When I revisit this code again, my hope would be to restructure so that it expresses the Signals API in terms of Elm 0.18's programs or effects managers ... there are some curious analogies between them that may or may not amount to something.