Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
"2:30pm. Right now I am just finishing vol 11 Dendro. Let me do that,…
… then the chores and I will resume. I was not sure whether to do more UI stuff today or to move to doing work on editor support, but I am glad I decided to do a little extra today. 3pm. Ok, enough slacking. That was fun. Let me do the chores here and I will resume. I am really quite perplexed about this current issue. I have no idea what the right thing to do would be. 3:20pm. Now where was I? Let me resume. Let me see if I got any replies. I am not sure what I am expecting here. ``` let model = { Count = 0; Step = 0; TimerOn = false } let update = Subject.behavior model update |> Observable.distinctUntilChangedKey (fun x -> x.TimerOn) |> Observable.map (fun x -> if x.TimerOn then Observable.interval(TimeSpan.FromSeconds(1.0)) else Observable.empty ) |> Observable.switch |> 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 ``` Got this from Asti. Let me try it out. I thought there would be something like this. 3:25pm. Oh, `Observable.switch` is exactly what I need. ...Ah, I remember. I read about `Switch` in the book, but I thought it selected just the latest between two observables. ``` |> Observable.distinctUntilChangedKey (fun x -> x.TimerOn) ``` It is also possible to do the `distinct` like this. I see. ``` |> Observable.switchMap (fun x -> if x.TimerOn then Observable.interval(TimeSpan.FromSeconds(1.0)) else Observable.empty ) ``` It is also possible to merge the switch and the map operations like so. ...No, this does not seem to work. 3:35pm. I'll open an issue on the F# page as this function is native to the F# library. Let me first plug this into the CounterApp command. 3:40pm. Surprisingly, it does not seem to work... ...Because I've forgot to use `switch`. 3:55pm. Did some replying let me open an issue on the F# repo. Opened issue 134 on the `FSharp.Control.Reactive` repo. 4:25pm. Damn, is it already this late? The weather is so perfect today. The winter is over, but the summer is not here yet. It is neither to hot or too cold. This is always the best time of the year. 4:45pm. I am still thinking about this. 4:50pm. Forget about pushing it in the middle of the pipeline. There is a thing I should try though. https://github.com/fsprojects/Fabulous/blob/master/Fabulous.XamarinForms/samples/CounterApp/CounterApp/CounterApp.fs Let me make the example more faithful to the original one. 4:55pm. ``` let update_cmd = pump |> Observable.scanInit (init,[]) (fun (model,_) msg -> match msg with | Increment -> { model with Count = model.Count + model.Step }, [] | Decrement -> { model with Count = model.Count - model.Step }, [] | Reset -> init, [] | SetStep n -> { model with Step = n }, [] | TimerToggled on -> { model with TimerOn = on }, (if on then [ TickTimer ] else []) | TimedTick -> if model.TimerOn then { model with Count = model.Count + model.Step }, [ TickTimer ] else model, [] ) |> Observable.startWith [init, []] ``` How about this? ``` let init = { Count = 0; Step = 1; TimerOn=false }, [] let pump = Subject.broadcast let dispatch msg = pump.OnNext msg let update_cmd = pump |> Observable.scanInit init (fun (model,_) msg -> match msg with | Increment -> { model with Count = model.Count + model.Step }, [] | Decrement -> { model with Count = model.Count - model.Step }, [] | Reset -> init | SetStep n -> { model with Step = n }, [] | TimerToggled on -> { model with TimerOn = on }, (if on then [ TickTimer ] else []) | TimedTick -> if model.TimerOn then { model with Count = model.Count + model.Step }, [ TickTimer ] else model, [] ) |> Observable.startWith [init] ``` This is better. It is a pity I have do drag commands into `scan`, but it can't be helped. 5pm. What is `consume` supposed to be for? 5:20pm. ``` module CounterApp.Try2 open System open System.Windows open System.Windows.Controls open System.Windows.Media open System.Reactive.Linq open System.Reactive.Disposables open FSharp.Control.Reactive /// Subscribers let do' f c = f c; Disposable.Empty let prop s v c = Observable.subscribe (s c) v let event s f c = (s c : IEvent<_,_>).Subscribe(fun v -> f c v) let children clear add set (v1 : IObservable<IObservable<IObservable<_>>>) c = // Note: The previous versions of this have bugs. let v2_disp = new SerialDisposable() new CompositeDisposable( v1.Subscribe(fun v2 -> clear c v2_disp.Disposable <- let v3_disp = new CompositeDisposable() let mutable i = 0 new CompositeDisposable( v2.Subscribe (fun v3 -> let i' = i v3_disp.Add <| v3.Subscribe (fun v -> if i' < i then set c i' v else i <- add c v + 1) ), v3_disp ) ), v2_disp ) :> IDisposable let ui_element_collection v1 c = children (fun (c : UIElementCollection) -> c.Clear()) (fun c -> c.Add) (fun c i v -> c.RemoveAt i; c.Insert(i,v)) v1 c /// Transformers let control'<'a when 'a :> UIElement> (c : unit -> 'a) l = Observable.Create (fun (sub : IObserver<_>) -> let c = c() let d = new CompositeDisposable() List.iter (fun x -> d.Add(x c)) l sub.OnNext(c) d :> IDisposable ) let control c l = control' c l :?> IObservable<UIElement> let stack_panel' props childs = control StackPanel (List.append props [fun c -> ui_element_collection childs c.Children]) let stack_panel props childs = stack_panel' props (Observable.ofSeq childs |> Observable.single) let window props content = control' Window (List.append props [prop (fun t v -> t.Content <- v) content]) /// The example type Model = { Count : int Step : int TimerOn : bool } type Msg = | Increment | Decrement | Reset | SetStep of int | TimerToggled of bool | TimedTick type CmdMsg = | TickTimer let init = { Count = 0; Step = 1; TimerOn=false } let pump = Subject.broadcast let dispatch msg = pump.OnNext msg let update_cmd = let init = init, [] pump |> Observable.scanInit init (fun (model,_) msg -> match msg with | Increment -> { model with Count = model.Count + model.Step }, [] | Decrement -> { model with Count = model.Count - model.Step }, [] | Reset -> init | SetStep n -> { model with Step = n }, [] | TimerToggled on -> { model with TimerOn = on }, (if on then [ TickTimer ] else []) | TimedTick -> if model.TimerOn then { model with Count = model.Count + model.Step }, [ TickTimer ] else model, [] ) |> Observable.startWith [init] let update = Observable.map fst update_cmd // Interesting tidbit - if you quckly check the checkbox on and off, it is possible to have the time go faster than it should. // So from that perspective the previous implementation was better. let cmd_timer = Observable.single TimedTick |> Observable.delay (TimeSpan.FromSeconds(1.0)) let cmd() = update_cmd |> Observable.map (snd >> Observable.toObservable) |> Observable.concatInner |> Observable.bind (function | TickTimer -> cmd_timer ) |> Observable.subscribe (fun x -> Application.Current.Dispatcher.Invoke(fun () -> dispatch x) ) let view = window [ do' (fun t -> t.Title <- "Counter App")] <| control Border [ do' (fun b -> b.Padding <- Thickness 30.0; b.BorderBrush <- Brushes.Black; b.Background <- Brushes.AliceBlue) prop (fun b v -> b.Child <- v) <| stack_panel [ do' (fun p -> p.VerticalAlignment <- VerticalAlignment.Center)] [ control Label [ do' (fun l -> l.HorizontalAlignment <- HorizontalAlignment.Center; l.HorizontalContentAlignment <- HorizontalAlignment.Center; l.Width <- 50.0) prop (fun l v -> l.Content <- v) (update |> Observable.map (fun model -> sprintf "%d" model.Count)) ] control Button [ do' (fun b -> b.Content <- "Increment"; b.HorizontalAlignment <- HorizontalAlignment.Center) event (fun b -> b.Click) (fun b arg -> dispatch Increment) ] control Button [ do' (fun b -> b.Content <- "Decrement"; b.HorizontalAlignment <- HorizontalAlignment.Center) event (fun b -> b.Click) (fun b arg -> dispatch Decrement) ] control Border [ do' (fun b -> b.Padding <- Thickness 20.0) prop (fun b v -> b.Child <- v) <| stack_panel [do' (fun p -> p.Orientation <- Orientation.Horizontal; p.HorizontalAlignment <- HorizontalAlignment.Center)] [ control Label [do' (fun l -> l.Content <- "Timer")] control CheckBox [ prop (fun c v -> c.IsChecked <- Nullable(v)) (update |> Observable.map (fun model -> model.TimerOn)) event (fun c -> c.Checked) (fun c v -> dispatch (TimerToggled true)) event (fun c -> c.Unchecked) (fun c v -> dispatch (TimerToggled false)) ] ] ] control Slider [ do' (fun s -> s.Minimum <- 0.0; s.Maximum <- 10.0; s.IsSnapToTickEnabled <- true) prop (fun s v -> s.Value <- v) (update |> Observable.map (fun model -> model.Step |> float)) event (fun s -> s.ValueChanged) (fun c v -> dispatch (SetStep (int v.NewValue))) ] control Label [ do' (fun l -> l.HorizontalAlignment <- HorizontalAlignment.Center) prop (fun l v -> l.Content <- v) (update |> Observable.map (fun model -> sprintf "Step size: %d" model.Step)) ] control Button [ do' (fun b -> b.HorizontalAlignment <- HorizontalAlignment.Center; b.Content <- "Reset") prop (fun b v -> b.IsEnabled <- v) (update |> Observable.map (fun model -> model <> init)) event (fun b -> b.Click) (fun b v -> dispatch Reset) ] ] ] [<STAThread>] let main _ = let a = Application() use __ = view.Subscribe (fun w -> a.MainWindow <- w; w.Show()) use __ = cmd() a.Run() ``` Here is the most direct translation I could think of. Quite nice. Let me go to the previous example. ``` let timerCmd() = update |> Observable.distinctUntilChangedKey (fun x -> x.TimerOn) |> Observable.map (fun model -> if model.TimerOn then Observable.interval(TimeSpan.FromSeconds(1.0)) else Observable.empty ) |> Observable.switch |> Observable.subscribe (fun _ -> Application.Current.Dispatcher.Invoke(fun () -> dispatch TimedTick) ) ``` I'll change this to... 5:25pm. ``` let cmd_timer = update |> Observable.distinctUntilChangedKey (fun x -> x.TimerOn) |> Observable.map (fun model -> if model.TimerOn then Observable.interval(TimeSpan.FromSeconds(1.0)) |> Observable.map (fun _ -> TimedTick) else Observable.empty ) |> Observable.switch let cmd() = Observable.mergeSeq [cmd_timer] |> Observable.subscribe (fun x -> Application.Current.Dispatcher.Invoke(fun () -> dispatch x) ) ``` This is perfect. The merge does do what I want right? Yeah, no doubt about it. 5:35pm. I am done thinking. I really like this example here. This is the pattern I would use to handle effects in real life programming. Very well. Let me close this thing. With this bit done, everything is set. I can move to editor support. I won't do any more examples, but maybe I will do a little writeup and post it on the F# sub. Let me push this here and I will accept that guy's answer. 5:50pm. I won't do any more programming for the day. All in all, I am glad I tackled this extra challenge. How about I add more stuff to the readme? 6:30pm. Done. Let me commit this so I can link to the commit. > `StackTenButtons.Try2` shows how create a scheduler for the UI thread in WPF. I can't find anywhere to fit this into the writeup. Oh well. 6:35pm. I am done for the day here. Let me do the committing, post a thread on the F# sub and I am done."
- Loading branch information