Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Behavior a -> Event b -> Dynamic a please? #140

Open
lspitzner opened this issue Nov 13, 2017 · 13 comments
Open

Behavior a -> Event b -> Dynamic a please? #140

lspitzner opened this issue Nov 13, 2017 · 13 comments

Comments

@lspitzner
Copy link
Contributor

Really i was looking for (a -> b -> c) -> Behavior a -> Dynamic b -> Dynamic c. Both are expressible via some combination of updated, attachWith, current, sample and buildDynamic, but i'd prefer something a bit higher-level.

(i base this mostly on the quickref, so perhaps i am missing something that isn't there.)

@3noch
Copy link
Member

3noch commented Nov 13, 2017

I've wanted this before as well. But I'm not sure there is a "safe" way of doing it (that doesn't suffer from high likelihood of cycles or being wrong).

@ryantrinkle
Copy link
Member

@lspitzner What are the semantics you're looking for? E.g.: if we produce out using this, when should updated out fire, and what should be the value of current out at all times?

@3noch
Copy link
Member

3noch commented Nov 14, 2017

I would imagine
mkDynamic (current x) (updated x) == x.

@ryantrinkle
Copy link
Member

I thought about these some more, and I think these functions can't be written.

In the case of f :: (a -> b -> c) -> Behavior a -> Dynamic b -> Dynamic c, suppose we do: dab = f (,) ba (pure b). The only Event in sight is updated (pure b), which is equivalent to never. Therefore, the updated dab cannot fire, since we can't spontaneously generate Event occurrences in pure code. The "Dynamic law" is that forall d, current d changes when and only when updated d fires, so current dab cannot ever change. This means that dab = pure ab for some ab = (a, b) :: (a, b). However, the only way we can get an a is from ba, and this is a pure function, which means it must operate identically regardless of when it is evaluated. As a result, it cannot choose any particular time at which to sample ba. Since ba may not be constant, a is ill-defined.

So, I believe that the only acceptable implementation for this function involves supplying undefined for a at least some (I would guess all, actually) of the time.

Note that this argument makes critical use of the Dynamic law, so, as you might expect, unsafeBuildDynamic allows you to get around this. This will only work properly if there is some special relationship between ba and the Dynamic b that ensures that dab will be lawful - and these things get really subtle.

For mkDynamic, the proof is simpler: in mkDynamic (current x) (updated x) == x', the only a we have is from current x :: Behavior a, since the second argument has type Event b (and a is not generally the same type as b). Since Behaviors are never prompt, we cannot produce updated x' == updated x.

@3noch
Copy link
Member

3noch commented Nov 14, 2017

What if mkDynamic :: Behavior a -> Event a -> Dynamic a? This is actually the function I've wanted. I've wanted something almost like holdDyn but that takes a Behavior a instead of a as its initial value. That's what this would be, effectively.

@ryantrinkle
Copy link
Member

The function with that exact type signature is unsafeDynamic. It's unsafe because it has no way to know whether your Event and Behavior obey the Dynamic law.

buildDynamic, on the other hand, is monadic, and I think it's got almost exactly the semantics you're describing. If you write d <- buildDynamic (sample b) e, you get a Dynamic whose value is the same as b when it's created, and changes when (and only when) e fires. It's the same semantics as v0 <- sample b ; holdDyn v0 e, except that it's lazier (with buildDynamic, the sample b is forced either when current d is forced or before the next frame, whichever comes first - whereas with the other approach, it's forced immediately).

@3noch
Copy link
Member

3noch commented Nov 14, 2017

Aha! Thank you. I did notice that unsafeDynamic fit the bill but I didn't want to use it for the obvious reason. But using buildDynamic seems like a great way as well!

@ryantrinkle
Copy link
Member

FWIW, I've found that unsafeDynamic is surprisingly difficult to use properly. I wouldn't recommend it if you have any other options.

@3noch
Copy link
Member

3noch commented Nov 14, 2017

It's aptly named. I don't think I'd ever dare to use it.

@lspitzner
Copy link
Contributor Author

lspitzner commented Nov 14, 2017

@ryantrinkle Right.

What about the versions that return monadic values? Are the following bad idea? (The names may be chosen badly, and I dislike the RankNTypes, but otherwise?)

dynamicPushAlways1
  :: (R.Reflex t, R.MonadHold t m)
  => R.Dynamic t a
  -> (forall m' . R.MonadSample t m' => a -> m' b)
  -> m (R.Dynamic t b)
dynamicPushAlways1 ad f =
  R.buildDynamic (f =<< R.sample (R.current ad)) (R.pushAlways f (R.updated ad))

dynamicPushAlways2
  :: (R.Reflex t, R.MonadHold t m, MonadFix m)
  => R.Dynamic t a
  -> (forall m' . (R.MonadHold t m', MonadFix m') => a -> m' b)
  -> m (R.Dynamic t b)
dynamicPushAlways2 ad f = do
  b0 <- f =<< R.sample (R.current ad)
  R.foldDynM (\a _ -> f a) b0 (R.updated ad)

dynamicAttachWith
  :: (R.Reflex t, R.MonadHold t m)
  => (a -> b -> c)
  -> R.Behavior t a
  -> R.Dynamic t b
  -> m (R.Dynamic t c)
dynamicAttachWith f ab bd = do
  a0 <- R.sample ab
  b0 <- R.sample (R.current bd)
  R.holdDyn (f a0 b0)
            (R.pushAlways (\b -> R.sample ab <&> \a -> f a b) (R.updated bd))

dynamicTag
  :: (R.Reflex t, R.MonadHold t m)
  => R.Behavior t a
  -> R.Event t b
  -> m (R.Dynamic t a)
dynamicTag ab be = do
  a0 <- R.sample ab
  R.holdDyn a0 (R.pushAlways (\_ -> R.sample ab) be)

(edit 1: removed some unnecessary constraints, make dynamicTag use Event in input)
(edit 2: Fix constraints in dynamicPushAlways1/2; they are different after all (!))

@ryantrinkle
Copy link
Member

@lspitzner Can you provide some examples of the situations where these would be used?

@lspitzner
Copy link
Contributor Author

No, not really :/ Most of the time one can invent some initial values so creating a Dynamic from some pushAlways'd Event is trivial. I'll have to look out for some good examples.

@ryantrinkle
Copy link
Member

My general sense is that, if I found myself needing dynamicPushAlways and dynamicAttachWith, I'd wonder whether I had made a mistake in some of the surrounding code. That's not to say that they're wrong, bad, or anything like that, just that it's tough to imagine when I'd need them.

dynamicTag is a bit more obvious, in that it seems to just sample the given behavior at creation and then whenever the given event fires. I can't think of a use case off the top of my head, but I suspect there are some. In this case, the question is just: can we think of a name that is shorter and more understandable than the implementation itself, and document it well? Having concrete use case examples would help with that, I think. Here's a better (lazier) implementation, by the way:

dynamicTag b e = buildDynamic (sample b) $ tag b e

@ryantrinkle ryantrinkle added this to the Later milestone Jan 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants