HeinrichApfelmus / threepenny-gui

GUI framework that uses the web browser as a display.
https://heinrichapfelmus.github.io/threepenny-gui/
Other
439 stars 77 forks source link

FRP combinators should not use `MonadIO` #91

Closed hanshoglund closed 8 years ago

hanshoglund commented 10 years ago

This issue is to argue against the current design of Reactive.Threepenny.

My objection is against the use of MonadIO in the accumulating methods, specifically the definition of accumE and accumB (and stepper and mapAccum which can be defined in terms of the former two functions).

accumE :: MonadIO m => a -> Event (a -> a) -> m (Event a)
accumB :: MonadIO m => a -> Event (a -> a) -> m (Behavior a)

I argue that these functions should be defined as follows (which is of course the definition used in reactive, reactive-banana and reenact – not in sodium), or removed.

accumE :: a -> Event (a -> a) -> Event a
accumB :: a -> Event (a -> a) -> Behavior a

The rationale is the usual rant. IO breaks abstraction as we all know. The precense of IO (in the guise of MonadIO m => m) is an abstraction leak.

I understand that there is a real-world compromise rationale for this API, but this in my view is a non-solution. I would go so far as to argue that the presence of these combinators ruin the (otherwise considerable) attractiveness of threepenny-gui as a simple, portable and functional GUI library.

HeinrichApfelmus commented 10 years ago

Alas, this issue is much more profound than it might seem at first.

First, I'd like to remark that the IO monad in MonadIO is an implementation detail and will be replaced by a more specialized Moment monad in a future version. In other words, the type signatures will read

accumE :: MonadMoment m => a -> Event (a -> a) -> m (Event a)

perhaps with a different name for the monad.

However, the fact that the three combinators return their event in a monad rather than as a plain value is hard to avoid. The main reason is that in the presence of dynamic event switching, some way to indicate starting times is required to avoid time leaks. Reactive-banana chooses a type parameter, but in the context of GUI programming, this would mean that every widget type has to be tagged with its time of creation, which seems prohibitively verbose as we would have to write an extra parameter Element t all the time. That's why Threepenny has opted to use a monad instead.

hanshoglund commented 10 years ago

Thanks for the elaboration. I would be happy with a non-IO type (including MonadIO and the like) wrapping the accumulators. You don't say what MonadMoment is, so I hope it will be something denotational.

I feel this issue is at heart of how FRP should be approached. I agree with you that time leaks are something that should be approach statically, but how still seems like an open question. As you say, restricting the accumulators is one way, restricting switcher is another. Maybe there are more?

hanshoglund commented 8 years ago

I think the general view nowadays (and the accepted implementation in reactive-banana and other libraries) is that past-dependent events/behaviors must be defined in a monad if switching is allowed (is there a paper that describes this insight?).

Anyway, closing this.