[Haskell-cafe] Arrow laws of Netwire
Ivan Perez
ivanperezdominguez at gmail.com
Tue Feb 20 08:40:05 UTC 2018
On 20 February 2018 at 03:33, Ivan Perez <ivanperezdominguez at gmail.com>
wrote:
>
>
> 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".
>>
>
I forgot to say: this is precisely how a definition of behaviour and
Monadic Stream are related. The fact that, regardless of the conceptual
definition, you'll be obtaining values progressively, always now, always
"towards" the future.
>
>> 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/e0646788/attachment.html>
More information about the Haskell-Cafe
mailing list