icz
icz

Reputation: 547

Elm - How to modify the parameterisation of one signal based on another signal

How can I parameterise one signal based on another signal? e.g. Suppose I wanted to modify the fps based on the x-position of the mouse. The types are:

Mouse.x : Signal Int
fps     : number -> Signal Time

How could I make Elm understand something along the lines of this pseudocode:

fps (Mouse.x) : Signal Time

Obviously, lift doesn't work in this case. I think the result would be Signal (Signal Time) (but I'm still quite new to Elm).

Thanks!

Upvotes: 2

Views: 203

Answers (2)

Apanatshka
Apanatshka

Reputation: 5958

Preamble

fps Mouse.x

Results in a type-error that fps requires an Int, not a Signal Int.

lift fps Mouse.x : Signal (Signal Int)

You are correct there. As CheatX's answers mentions, you cannot using these "nested signals" in Elm.

Answer to your question

It seems like you're asking for something that doesn't exist yet in the Standard Libraries. If I understand your question correctly you would like a time (or fps) signal of which the timing can be changed dynamically. Something like:

dynamicFps : Signal Int -> Signal Time

Using the built-in functions like lift does not give you the ability to construct such a function yourself from a function of type Int -> Signal Time.

I think you have three options here:

  1. Ask to have this function added to the Time library on the mailing-list. (The feature request instructions are a little bloated for a request of such a function so you can skip stuff that's not applicable)
  2. Work around the problem, either from within Elm or in JavaScript, using Ports to connect to Elm.
  3. Find a way to not need a dynamically changing Time signal.

I advise option 1. Option 3 is sad, you should be able to what you asked in Elm. Option 2 is perhaps not a good idea if you're new to Elm. Option 1 is not a lot of work, and the folks on the mailing-list don't bite ;)

To elaborate on option 2, should you want to go for that:

  1. If you specify an outgoing port for Signal Int and an incoming port for Signal Time you can write your own dynamic time function in JavaScript. See http://elm-lang.org/learn/Ports.elm
  2. If you want to do this from within Elm, it'll take an uglier hack:

    dynamicFps frames = 
      let start = (0,0)
          time  = every millisecond -- this strains your program enormously
          input = (,) <~ frames ~ time
          step (frameTime,now) (oldDelta,old) = 
            let delta = now - old
            in if (oldDelta,old) == (0,0)
                 then (frameTime,now) -- this is to skip the (0,0) start
                 else if delta * frameTime >= second
                        then (delta,now)
                        else (0,old)
      in dropIf ((==) 0) 0 <| fst <~ foldp step start input
    

    Basically, you remember an absolute timestamp, ask for the new time as fast as you can, and check if the time between the remembered time and now is big enough to fit the timeframe you want. If so you send out that time delta (fps gives time deltas) and remember now as the new timestamp. Because foldp sends out everything it is to remember, you get both the new delta and the new time. So using fst <~ you keep only the delta. But the input time is (likely) much faster than the timeframe you want so you also get a lot of (0,old) from foldp. That's why there is a dropIf ((==) 0).

Upvotes: 4

CheatEx
CheatEx

Reputation: 2124

Nested signals are explicitly forbidden by the Elm's type system [part 3.2 of this paper].

As far as I understand FRP, nested signals are only useful when some kind of flattering provided (monadic 'join' function for example). And that operation is hard to be implemented without keeping an entire signal history.

Upvotes: 0

Related Questions