5 ms·
What I love about Monad and Applicative interfaces is that I can read that code and almost immediately fully grasp what’s going on without having a clue about t
by sfvisser 8y ago
What I love about Monad and Applicative interfaces is that I can read that code and almost immediately fully grasp what’s going on without having a clue about the underlying library.
There are a bunch of (well named) functions and control flow. No boilerplate.
- danidiaz 8y agoI have a small quibble about the names though—or perhaps I didn't fully understand the example. Since "inverterPowerChange" returns a Behaviour http://hackage.haskell.org/package/reactive-banana-1.2.0.0/docs/Reactive-Banana-Combinators.html#t:Behavior http://hackage.haskell.org/package/reactive-banana-1.2.0.0/d... which is supposed to be "inspectable" at any point in time, shouldn't PowerChange, PowerOff and PowerOn actually be PoweredState, PoweredOff and PoweredOn? The former sound more like event names. Or perhaps should "inverterPowerChange" return an Event instead? http://hackage.haskell.org/package/reactive-banana-1.2.0.0/docs/Reactive-Banana-Combinators.html#t:Event http://hackage.haskell.org/package/reactive-banana-1.2.0.0/d...
- endgame 8y agoYes, I think your alternate names make more sense when you think about `Behavior`s only. That said, from what I understand of FRP you don't want to think of the data type as "type T is for Behaviors" or "type S is only for Events", because it's so easy to compose them. If I have even an `Event ()` (an event source with no data beyond the fact that it fired), I can use (<@) to view the `Behavior T`, and now I have an `Event T`. If the function in OP was returning an Event, it would be a sequence of instructions to the freezer: "turn on now", "turn off now" and so on. As a Behavior, it's more like a set of times when the fridge should be on or off.
- danidiaz 8y agoAh I see. Events form a Monoid, so I guess I could have a "something happens" event aggregating all the sensor updates, and sample the "inverterPowerChange" Behaviour at the occurrences of the Event. The API for reactive-banana doesn't seem to have a "value changed" combinator that creates an Event from a Behaviour alone; one must sample the Behaviour with another Event. (Other FRP libraries somewhat similar in philosophy like reflex—not the JS Reflex—do seem to have Behaviour-like things that can fire events on change: http://hackage.haskell.org/package/reflex-0.4.0.1/docs/Reflex-Dynamic.html#v:updated http://hackage.haskell.org/package/reflex-0.4.0.1/docs/Refle... Although reactive-banana seems simpler to understand.)
- joeyh 8y agoreactive-banana does have a function from Behavior a -> Event (Future a) but that Future gets in the way and removing it is only for wizards. On the other hand, I think that's necessary for mutually recursive definitions of behaviors and events to reach a fixed point, which is quite a neat feature although not one I've needed yet.
- endgame 8y agoA piece of folk wisdom I picked up along the way was you often end up needing a "Behavior a" and an "Event a" that fires when the Behavior changes. The reason I switched from dabbling-with-reactive-banana to dabbling-with-reflex is exactly that I needed "Dynamic"s.The semantics for Behavior require that it doesn't appear to change value until the frame has finished updating, so you either need to keep an Event around that fires the "new value" in the current frame or use a more powerful library. Remark: Event is a Monoid or a Semigroup only when the contained value is either a Semigroup. The most useful combining ability comes from Behavior's Applicative instance.
- joeyh 8y agoAnother way to look at this is that while the Behavior PowerChange is continuous, once it gets wired up to a switch or something, the reaction to the Behavior at any point may change the state of the switch (particularly if something else might have affected the state; including eg some human pressing a physical button). So a Behavior PowerChange is the potential changes that might occur. Still, I will think about renaming PowerChange, perhaps it could be clearer.
- jstimpfle 8y ago> no boilerplate Well, compare to PowerChange inverterPowerChange(Sensors *sensors) { if (sensors->lowpower) return PowerOff; if (sensors->wifiusers > 0) return PowerOn; return sensors->fridgePowerChange; } in a more traditional language. Nobody would make a fuss about that code.
- danidiaz 8y agoThe reactive-banana code does more things; it also does the equivalent (in Observer pattern terms) of adding observers to the corresponding observables.
- kryptiskt 8y agoIt doesn't do that. The one in the post composes a bunch of other Behaviors, see the lowPower one further down in the article for an example, it's not polling a bunch of variables. Then it is all wired up to react to events.
- darkkindness 8y agoI think that, while the lack of type annotations on every variable is great since hiding boilerplate is great, it could be confusing to the uninitiated. To elaborate, lowpower etc. on this line are Behaviors, which emit values by time: react <$> lowpower <*> fridgepowerchange <*> wifiusers while lowpower, fridgepowerchange, wifiusers in the react function bind to the values that are emitted by the Behaviors. (Just by looking at the code we can infer that their types are Bool, (Num a, Ord a) => a, and PowerChange respectively.) react lowpower fridgepowerchange wifiusers | lowpower = Just PowerOff | wifiusers > 0 = Just PowerOn | otherwise = fridgepowerchange None of these are simply state variables! Although this might have confused GP, I think this is nice since we can associate the name 'lowpower' for its semantics (The low power signal) and not have to worry about "this lowpower is a Behavior while that lowpower is a Bool".
- tantalor 8y agoSo is it confusing or obvious? Pick one