[Haskell-cafe] Arrow laws of Netwire

Ivan Perez ivanperezdominguez at gmail.com
Tue Feb 20 08:33:38 UTC 2018


On 19 February 2018 at 23:17, Ertugrul Söylemez <esz at posteo.de> wrote:

> Hi Ivan,
>
> >> The easiest way to see the difference is by looking at some of the
> >> combinators.  Notice that things like 'hold', 'scan'/'accum', and
> >> 'tag' are real functions.  In a first-class FRP system these would
> >> have types like the following:
> >>
> >>     hold :: a -> Event a -> Moment (Behaviour a)
> >>     scan :: a -> Event (a -> a) -> Moment (Event a)
> >>     tag  :: Behaviour (a -> b) -> Event a -> Event b
> >>
> >> The Moment monad is not inherent to the way the underlying state
> >> machine is constructed, but acts merely as a provider for the notion
> >> of "now".  Since 'tag' doesn't need that notion, it's a completely
> >> pure function.
> >
> > Well, in a way. Yes, it can be a pure function, and an event can
> > somehow be a delayed computation of how/when it is actually produced,
> > computed/consumed the moment you want to actually evaluate the
> > network.
> >
> > Saying that they are pure would be just fine if Behaviours did not
> > depend on the outside world (that is, if they were "calculated" from
> > pure haskell functions). But I don't think they are. Not always. Not
> > if you want to depend on any external user input.
>
> Behaviours are actually pure values.  They don't really depend on time
> or any effects.  Their values may very well be generated from effects,
> for example the "current cursor position", but conceptually the
> behaviour that represents the whole timeline of values is indeed a pure
> value.
>

I know how they are conceptually defined, but I think the word pure was
stretched a lot here to fit this model, not the other way around. See [6].

Are we discussing Classic FRP as a concept, or as it is normally
implemented?


> There is a caveat of course:  We like to think of behaviours as
> functions of time, but that's not the whole truth, because our
> capability to observe the value of a behaviour is very limited, in most
> implementations to an abstract notion of "now".  The same is true for
> events: we can only ever ask whether an event is happening "now".
>
> That's how effects and a pure API can be compatible.  We can think of
> behaviours as pure timelines (or functions of time), but the API cannot
> possibly give us full access to it.
>
>
> > In Reflex (and I'm not trying to discuss the particularities of this
> > implementation), yes, Behaviour and Event are types in a family, but
> > the actual definitions in Spider I can seee are records of IORefs with
> > bangs.  Far from pure.
>
> Yes, of course.  The implementation is shockingly impure and hacky,
> which is why there is such a massive test suite. =)
>
> There are much less hacky ways to implement it, but unfortunately some
> impurity is inevitable.  The reason for Spider's hacikness is
> efficiency: Reflex is incredibly fast, and a lot of effort went into
> only ever computing things that matter, and never computing them twice.
> In my benchmarks it comes very close to wires, which is quite
> impressive, if you consider what thin an abstraction layer Wire (or MSF)
> is.
>
>
> >> You can have that function in AFRP as well:
> >>
> >>     fmap :: (a -> b) -> Event a -> Event b
> >>
> >> However, unlike 'fmap', 'tag' makes sense in a pure context.  You can
> >> pass an Event and a Behaviour to a different thread via an MVar,
> >> combine them there, then send the result back, and it will still work
> >> in the context of the greater application (no isolated state
> >> machines).
> >
> > I don't see how you cannot do that with wires. For instance, you can
> > send a Wire m () (Event b), and a Wire m () (a -> b), and compose them
> > in a pure context. Then you can bring that back and use it.
>
> Right.  The difference is that you need to be very careful about
> context.  If you have a "main wire", you must make sure to communicate
> that result back into it *or* run two wires concurrently.  This caution
> is not necessary with first-class FRP, because it does not have that
> context-sensitivity.
>

That is only possible if, at the time of polling or connecting to the
outside world, someone has done the job of avoiding double polling. Which
you can do in the monad in wires, and get the same benefit.

>> You can hold an event in any concurrent thread, etc.
> >
> > Can you use it without doing IO and executing the computation
> > associated to calculating/polling the behaviour? If so, it must be
> > because the FRP evaluation method has some inherent thread-safety (I
> > you need IO + more for that). Wouldn't you be able to put that thread
> > safety in your monad, and then use it with MSFs/Wires?
>
> Thread safety is a different matter, and yes, the implementation must be
> thread-safe for that to work.  This is the reason why I was
> investigating an FRP implementation based on STM to see how fine-grained
> regions would pan out, but it was so slow that i abandoned that
> approach.


I've used this for F;RP (the comma important) and the results were ok. For
widget-based GUIs, this is fast enough. For games, probably not (haven't
tried large games).


>   Reflex does global locking, which sucks, but I can't think of
> a better way.
>
> To answer your question: it depends on the controller API of the
> framework.  For example in Reflex the frame boundary is created by
> 'fireEventsAndRead'.  This is the only action that can "advance time".
> You can use it from multiple threads, and it will have a timeline-global
> effect (you can have multiple timelines in Reflex, but if that doesn't
> make sense to you, just think of "timeline-global" as "global").
>
> In reactive-banana the frame boundery is created by registered
> callbacks.  R-b registers callbacks for events that matter (that's where
> 'fromAddHandler' and 'reactimate' meet), and whenever one of them is
> invoked, a new frame begins.
>
> In both cases the clock ticks as events fire.
>
>
> >> Another example is that if the underlying monad is nontrivial (say
> >> IO) you can't easily split behaviours in a pure context in AFRP.
> >
> > You can, but you need a monad such that: (,) <$> ma <*> ma == (\x ->
> > (x,x)) <$> ma.
> >
> > Is this called idempotent?
> >
> > But to implement any form of Classic FRP or Reactive Programming on
> > top of MSFs, you want that kind of monad.
>
> Not sure if idempotency is the right term, but in any case you have that
> monad in fist-class FRP.  It's called Behavio(u)r. =)
>

A behaviour is stronger than this.

What I am giving is the broadest characterisation of a monad with the
property we want.

>
> Note: The Monad instance for Behavior is not implemented yet in Reflex
> 0.4.0, but you can easily achieve the same by using 'pull' and 'sample':
>
>     pull (liftA2 (,) (sample b1) (sample b2))
>
> The instance is implemented in the git version.
>
>
> >> This restriction does not exist in first-class FRP:
> >
> > Well, it is not exposed to the user, but someone must have thought
> > about it and solved it. Duplication of effects is inherent to having
> > monadic computations associated to obtaining the values of
> > behaviours. If you don't cache for a given timestamp, you duplicate
> > effects.
>
> This is only really inherent to the mealy-machine approach (i.e. "what
> AFRP does").  The monads involved in first-class FRP really only serve
> to tie reactive combinators to "now".  Their implementations only
> control when exactly (in which frame) you hold an event, which is
> usually a simple matter of effect sequencing, i.e. "having a monad".  In
> other words: moment monads are generally just IO in disguise.
>

If I depend on the same external behaviour (e.g. mouse position) from two
parts of my program at the same time, what prevents the mouse position from
being polled twice?


> I cannot say I like arrow notation, or inputs based on tuples. We need
> > more work on this.
> >
> > However, I decided to embrace the A and I am finding a lot of
> > extensions and guarantees that are possible, or easier, thanks to
> > that.
>
> Cale Gibbard has done some work on desugaring arrow notation in smarter
> ways than the tuple-based approach we have now, but ultimately the whole
> arrow approach was abandoned (and eventually Reflex was born).
>

Aha! That is interesting. Do you have a pointer to find that work?


>
> My original approach with Netwire was to provide higher-level
> composition capabilities to reduce the amount of "side channels"
> necessary, which lead to an interesting Alternative instance for
> Netwire's version of Wire.  One of the defining features of Netwire is
> the ability to "inhibit", which facilitates a form of switching that
> eliminates most use cases of Yampa's event-based switches.  The
> following is a string-valued wire that displays "---", but every five
> seconds it switches to "Ding!" temporarily for one second:
>
>     ("Ding!" . holdFor 1 <|> "---") . periodic 5
>
> However, nowadays I think first-class FRP is the superior approach.
>
>
> Greets
> ertes
>

[6] https://dl.acm.org/citation.cfm?id=3110246
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20180220/575c38be/attachment.html>


More information about the Haskell-Cafe mailing list