joews
joews

Reputation: 30330

Filter a signal by the value of another signal

I want to create a filtered Signal for mouse position. It should update when a mouse button is down, or the mouse transitions from "not down" to "down".

I came up with this function. It works, but using three anonymous functions doesn't seem right. Is there an idiomatic way to do this?

mouseDownPosition: Signal (Int, Int)
mouseDownPosition = 
  Signal.map2 (\(x, y) isDown -> (x, y, isDown)) Mouse.position Mouse.isDown
  |> Signal.filter (\(x, y, isDown) -> isDown) (0, 0, False)
  |> Signal.map (\(x, y, isDown) -> (x, y))

Upvotes: 1

Views: 192

Answers (3)

Apanatshka
Apanatshka

Reputation: 5958

Library function

There is a utility library for signals called signal-extra (full disclosure: I'm the author of that library), which has a function for just this: keepWhen: Signal Bool -> a -> Signal a -> Signal a.

[...] filter that keeps events from the Signal a as long as the Signal Bool is true.

Implementation

If you're wondering, the implementation does almost the same as you wrote yourself, but there is a subtle difference!

keepWhen : Signal Bool -> a -> Signal a -> Signal a
keepWhen boolSig a aSig =
  zip boolSig aSig
  |> sampleOn aSig
  |> keepIf fst (True, a)
  |> map snd

The zip is just a Signal.map2 (,) to combine the two signals. The keepIf is an old (IMHO clearer) name for Signal.filter. So that's all basically the same. But after zipping the two signals together, there is a sampleOn. That's there so the result of filtering is a signal that only updates when the original value signal aSig updates, and doesn't update when the boolSig updates.

Alternative semantics

There is another filter called sampleWhen: Signal Bool -> a -> Signal a -> Signal a, which has the description:

A combination of Signal.sampleOn and keepWhen. When the first signal becomes True, the most recent value of the second signal will be propagated.

This alternative matches with the code that you wrote, but it doesn't match your description "Its value should change only when a mouse button is down".

Now it's up to you to figure out which behaviour you were really looking for :)

Upvotes: 2

pdamoc
pdamoc

Reputation: 2923

I like @joews solution better but just for the sake of an alternative, here is another version:

mouseDownPosition: Signal (Int, Int)
mouseDownPosition = 
  Signal.map2 (\p d -> if d then Just p else Nothing) Mouse.position Mouse.isDown
  |> Signal.filterMap identity (0,0)

Upvotes: 2

joews
joews

Reputation: 30330

Whilst posting the question I worked out how to avoid anonymous functions. I have a feeling there is a more elegant solution, though.

mouseDownPosition: Signal (Int, Int)
mouseDownPosition = 
  Signal.map2 (,) Mouse.position Mouse.isDown
  |> Signal.filter snd ((0, 0), False)
  |> Signal.map fst

Upvotes: 1

Related Questions