fsprojects / FSharp.Control.Reactive

Extensions and wrappers for using Reactive Extensions (Rx) with F#.
http://fsprojects.github.io/FSharp.Control.Reactive
Other
284 stars 60 forks source link

`switchMap` does not act like `map` followed by `switch` #134

Closed mrakgr closed 4 years ago

mrakgr commented 4 years ago

Description

open System
open FSharp.Control.Reactive

type Model = {
    Count : int
    Step : int
    TimerOn : bool
    }

let model = { Count = 0; Step = 0; TimerOn = false }
let update = Subject.behavior model

update
|> Observable.distinctUntilChangedKey (fun x -> x.TimerOn)
|> Observable.switchMap (fun x -> 
    if x.TimerOn then 
        Observable.interval(TimeSpan.FromSeconds(1.0)) 
    else 
        Observable.empty
)
|> Observable.subscribe (fun i -> printfn "Tick %d" i)
|> ignore

while true do
    printfn "Start (y)?"
    let switch = Console.ReadLine()
    update 
    |> Subject.onNext( { model with TimerOn = switch = "y" })
    |> ignore

Repro steps

Please provide the steps required to reproduce the problem

  1. Run the program, press 'y' and then 'enter'.

Expected behavior

Start (y)?
y
Start (y)?
Tick 0
Tick 1
Tick 2

And so on.

Actual behavior

Start (y)?
y
Start (y)?

No further output.

Known workarounds

|> Observable.switchMap (fun x -> 
    if x.TimerOn then 
        Observable.interval(TimeSpan.FromSeconds(1.0)) 
    else 
        Observable.empty
)

Replace the above with...

|> Observable.map (fun x -> 
    if x.TimerOn then 
        Observable.interval(TimeSpan.FromSeconds(1.0)) 
    else 
        Observable.empty
)
|> Observable.switch

Related information

Version 4.2.0 of the FSharp.Control.Reactive.

deviousasti commented 4 years ago

I'd like to point out that the root of the problem is the use of Observable.empty, which switchMap seems unable to recover from.

NickDarvey commented 4 years ago

I'm getting similar behaviour but I'm not using Observable.empty

Doesn't work:

|> Observable.switchMap (fun term ->
        if term.Length = 0 then Observable.single []
        else Observable.ofAsync <| options.lookup term)

Does work:

|> Observable.map (fun term ->
        if term.Length = 0 then Observable.single []
        else Observable.ofAsync <| options.lookup term)
|> Observable.switch

Version 4.2.0 too.