Skip to content

Commit

Permalink
"2:30pm. Right now I am just finishing vol 11 Dendro. Let me do that,…
Browse files Browse the repository at this point in the history
… 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
mrakgr committed Apr 10, 2020
1 parent 9965d5e commit 47548e2
Showing 1 changed file with 1 addition and 1 deletion.
2 changes: 1 addition & 1 deletion The Spiral Language v0.2/Script1.fsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@


0 comments on commit 47548e2

Please sign in to comment.