question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Does FuncUI really need the MSG type DU's. Can't dispatch just use functions

See original GitHub issue

Here’s your counter example rewritten just using functions instead of DU’s. It removes the need for a centralised msg handler.

namespace CounterElmishSample

open System
open Avalonia.Controls
open Avalonia.Controls
open Avalonia.FuncUI.DSL
open Avalonia.FuncUI.Components
open Avalonia.FuncUI.Components
open Avalonia.FuncUI.DSL
open Avalonia.FuncUI.DSL
open Avalonia.FuncUI.Types
open Avalonia.Layout

type CustomControl() =
    inherit Control()

    member val Text: string = "" with get, set

[<AutoOpen>]
module ViewExt =
    ()

module Counter =
    open Avalonia.FuncUI.DSL
    
    type CounterState = {
        count : int
        numbers: int list
    }

    let init = {
        count = 0
        numbers = [0 .. 100_000]
    }

    let increment state = {state with count = state.count + 1}    
    let decrement state = {state with count = state.count - 1}
    let specific number_state = {state with count = number }
    let remove_number = { state with numbers = List.except [number] state.numbers }
    
    let view (state: CounterState) (dispatch) =
        DockPanel.create [
            DockPanel.children [
                ListBox.create [
                    ListBox.items state.numbers
                    ListBox.itemTemplate (
                        TemplateView.create(fun data ->
                            let data = data :?> int 
                            DockPanel.create [
                                DockPanel.children [
                                    Button.create [
                                        Button.content "delete"
                                        Button.dock Dock.Right
                                        Button.width 50.0
                                        Button.tag data
                                        Button.onClick (fun args ->
                                            let number = (args.Source :?> Button).Tag :?> int
                                            dispatch remove_number number
                                        )
                                    ]                                    
                                    TextBlock.create [
                                        TextBlock.text (sprintf "%A" data)
                                        TextBlock.width 100.0
                                    ]                                    
                                ]
                            ]
                            |> generalize 
                        )                  
                    )
                ]
                (*
                TextBox.create [
                    TextBox.dock Dock.Bottom
                    TextBox.text (sprintf "%i" state.count)
                    TextBox.onTextChanged (fun text ->
                        printfn "new Text: %s" text
                     )
                ]
                TextBlock.create [
                    TextBlock.dock Dock.Top
                    TextBlock.fontSize 48.0
                    TextBlock.foreground "blue"
                    TextBlock.verticalAlignment VerticalAlignment.Center
                    TextBlock.horizontalAlignment HorizontalAlignment.Center
                    TextBlock.text (string state.count)
                ]
                LazyView.create [
                    LazyView.args dispatch
                    LazyView.state state.count
                    LazyView.viewFunc (fun state dispatch ->
                        let view = 
                            TextBlock.create [
                                TextBlock.dock Dock.Top
                                TextBlock.fontSize 48.0
                                TextBlock.foreground "green"
                                TextBlock.verticalAlignment VerticalAlignment.Center
                                TextBlock.horizontalAlignment HorizontalAlignment.Center
                                TextBlock.text (string state)
                            ]
                            
                        view |> fun a -> a :> IView
                    )
                ]
                *)
            ]
        ]       

Is there an advantage to using the DU’s here that just plain function composition can’t do? For simple functions it is even possible to inline the operations.

Instead of

 Button.onClick (fun args ->
   let number = (args.Source :?> Button).Tag :?> int
   dispatch remove_number number
  )

you could write

 Button.onClick (fun args ->
   let number = (args.Source :?> Button).Tag :?> int
   dispatch (fun state -> { state with numbers = List.except [number] state.numbers })
  )

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
JaggerJocommented, Oct 10, 2019

I personally like the centralised approach of having an update function. I see several benefits of doing it that way:

  • the view function contains no business logic only view logic
  • business logic is centralised per module or view in the corresponding update function
  • you can easily unit test your logic, because its not coupled to the view

You are also free to roll you own State & Update management. I actually do this for the drawing app you see in the screenshots. I basically have a lot of different Messages that trigger complex operations. Messages and Operations are also in a different Project and not UI dependent.

I could basically write a web version and use $(FSharp MVU Framework) without touching the Logic Project.

1reaction
bradphelancommented, Oct 15, 2019

I did a small example of uisng FuncUI without messages. The comment I made above was done without actually trying to compile it. I’ve a fork with a change to the Counter example. Maybe it’s interesting

https://github.com/bradphelan/Avalonia.FuncUI/commit/f6de6438a3c7ea3b99d357cfe846c1d62dd5dd38

My update functions are defined

    let increment  (state: CounterState) : CounterState = { state with count =  state.count + 1 }
    let decrement  (state: CounterState) : CounterState = { state with count =  state.count + 1 }
    let set_count  (state:CounterState) count : CounterState = { state with count = count;}

and the view is defined

    let view (state: CounterState) (dispatch): View =
        Views.dockpanel [
            Attrs.children [
                Views.button [
                    Attrs.dockPanel_dock Dock.Bottom
                    Attrs.onClick (Binder.command increment dispatch)
                    Attrs.content "-"
                ]
                Views.button [
                    Attrs.dockPanel_dock Dock.Bottom
                    Attrs.onClick (Binder.command decrement dispatch)
                    Attrs.content "+"
                ]
                Views.textBox[
                    Attrs.dockPanel_dock Dock.Top
                    Attrs.fontSize 48.0
                    Attrs.verticalAlignment VerticalAlignment.Center
                    Attrs.horizontalAlignment HorizontalAlignment.Stretch
                    Attrs.text (string state.count)
                    Attrs.onKeyUp (Binder.oneWay 0 set_count dispatch )
                ]
            ]
        ]       

I created a Binder object for managing commands and bindings. Just an experiment.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Does FuncUI really need the MSG type DU's. Can't dispatch just ...
Here's your counter example rewritten just using functions instead of DU's. It removes the need for a centralised msg handler. namespace CounterElmishSample ...
Read more >
fsprojects/Avalonia.FuncUI: Develop cross-plattform GUI ...
FuncUI is a thin layer built on top of AvaloniaUI. It contains abstractions for writing UI applications in different programming styles. It also...
Read more >
Defunctionalisation: An underappreciated tool for writing ...
The trouble is really that defunctionalisation is much, much easier if you've got sum types, pattern-matching, and higher-order functions.
Read more >
Fast single dispatch to get around multiple dispatch at ...
3) Unityper.jl is cool, but it combines types into 1, so it seems I can't write a multimethod, just different functions for each...
Read more >
Functions as Redux actions - by Thai Pangsakulyanont
Redux does't like it when you dispatch a non-object into a store. So I'm going to cheat by dispatching only one action type...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found