Closed drewolson closed 4 years ago
I ended up writing a helper function called race
that, I think, does what I want. It takes a starting value and a Traversable
of Signal
s. It returns the value from any of the signals that differs from the starting value. Let me know if this seems crazy.
race
:: forall a v f
. Eq a
=> Monoid v
=> Traversable f
=> a
-> f (Signal v a)
-> Signal v a
race val signals = do
values <- sequence signals
let match = find ((/=) val) values
pure $ fromMaybe val match
I'm not sure how race
helps here. Signals are combined using monadic bind, and you always have access to all the output vals from signals. There's no point in only returning the first value that is different from the starting value.
I reimplemented your gist using Signals. Hope that helps clarify things -
mainWidget :: forall a. Widget HTML a
mainWidget = do
dyn $ loopS 0 \n -> do
display $ D.text (show n)
n' <- incrementTicker n
counterSignal n'
-- Counter
counterSignal :: Int -> Signal HTML Int
counterSignal init = loopW init $ \n -> D.div'
[ n+1 <$ D.button [P.onClick] [D.text "+"]
, D.div' [D.text (show n)]
, n-1 <$ D.button [P.onClick] [D.text "-"]
]
-- Timer
incrementTicker :: Int -> Signal HTML Int
incrementTicker init = loopW init $ \n -> do
liftAff $ delay $ Milliseconds 1000.0
pure (n+1)
Some notes -
A Signal
is basically a Widget
+ looping
. So loopW
, which provides stateful looping, is the easiest way to generate individual signals.
Signals are "continuous", i.e. they always have a current value. So the notion of "merging" signals does not arise except as a zipping operation, where you provide a continuous function which combines individual values from the constituent signals. This "zipping" is possible using monadic bind like so -
do
a <- signal1
b <- signal2
c <- signal3
pure (someFunction a b c)
signal2
depends on the output from signal1
, and signal3
depends on the output of both signal1
and signal2
then -do
a <- signal1
b <- signal2 a
c <- signal3 a b
pure (someFunction a b c)
loopS
combinator which "loops" the value of the last signal back up to the top so that the upstream signals can use it. Note that you would need the last signal to carry all the values that are needed by the upstream signals.Usually you would have some sort of a state that is accessible to all the constituent signals, and loop over that state. In the counter+timer example in my previous comment, the current value of the timer as well as the counter depended on the current count. So we need to loop that over.
map
the final composition function on top of the signal value.To take a pedantic example - if signal1
also depended on the values of signal2
and signal3
, we can have signal3
return those values and make them available to signal1
with loopS
. Let's assume that the initial values for signal2
is initialB
and for signal3
is initialC
.
loopS {b:initialB, c:initialC} \{b, c} -> do
a <- signal1 b c
b' <- signal2 a
c' <- signal3 a b'
pure {b:b', c:c'}
But now the final value of the signal is Tuple b c
. So we need to fix that. We need to add the value of signal1
to the output since that's needed in the final output, and then map over with someFunction
-
s = g <$> innerSignal
where
g {a,b,c} = someFunction a b c
innerSignal = loopS {a:initialA, b:initialB, c:initialC} \{b, c} -> do
a' <- signal1 b c
b' <- signal2 a'
c' <- signal3 a' b'
pure {a:a', b:b', c:c'}
Not pretty, but usually you won't have such complicated state and dependencies.
Thanks, this is hugely helpful. I think you could take this answer and put it directly into the Signal
s section of the documentation, it's great.
A quick clarifying question, regarding your solution to my problem:
dyn $ loopS 0 \n -> do
display $ D.text (show n)
n' <- incrementTicker n
counterSignal n'
In this case, counterSignal
depends on the output from incrementTicker
because it will have to update if incrementTicker
ticks. But, just to be clear, this doesn't mean that counterSignal
waits for the tick, correct? The bind essentially just "wires them up" but doesn't have any composition in time (as you noted in your documentation). So, intuitively, the bind
onincrementTicker
always produces some value, but that value would just be the initial n
unless the tick has fired.
Is my understanding correct?
Thanks again for taking the time to put together this fantastic explanation.
Yup, correct. Both the widgets run simultaneously. Until incrementTicker fires, the initial value is used, and when the incrementTicker value changes, the counterSignal is restarted with the new value so it's always up to date.
I will put this in the documentation. Thanks for the feedback!
Given two signals of the same type, is there a way to merge them into another signal that emits events from both?
I'm attempting to reimplement https://gist.github.com/drewolson/607c7e742c471400e0e61c5068e0728d using signals, but I'm not sure what the equivalent of this line is with signals:
Signal doesn't seem to have an instance of
Alt
, so I'm not sure what I should do here.