1

I'm new to F# and Bolero. and after initialize the project as the docs shows, I find that sample application demonstrates 3 types of web applications but main codes is in one file called Main.fs .

here is part codes:

type Message =
    | SetPage of Page
    | Increment
    | Decrement
    | SetCounter of int
    | GetBooks
    | GotBooks of Book[]
    | SetUsername of string
    | SetPassword of string
    | GetSignedInAs
    | RecvSignedInAs of option<string>
    | SendSignIn
    | RecvSignIn of option<string>
    | SendSignOut
    | RecvSignOut
    | Error of exn
    | ClearError

let update remote message model =
    let onSignIn = function
        | Some _ -> Cmd.ofMsg GetBooks
        | None -> Cmd.none
    match message with
    | SetPage page ->
        { model with page = page }, Cmd.none

    | Increment ->
        { model with counter = model.counter + 1 }, Cmd.none
    | Decrement ->
        { model with counter = model.counter - 1 }, Cmd.none
    | SetCounter value ->
        { model with counter = value }, Cmd.none

    | GetBooks ->
        let cmd = Cmd.OfAsync.either remote.getBooks () GotBooks Error
        { model with books = None }, cmd
    | GotBooks books ->
        { model with books = Some books }, Cmd.none

    | SetUsername s ->
        { model with username = s }, Cmd.none
    | SetPassword s ->
        { model with password = s }, Cmd.none
    | GetSignedInAs ->
        model, Cmd.OfAuthorized.either remote.getUsername () RecvSignedInAs Error
    | RecvSignedInAs username ->
        { model with signedInAs = username }, onSignIn username
    | SendSignIn ->
        model, Cmd.OfAsync.either remote.signIn (model.username, model.password) RecvSignIn Error
    | RecvSignIn username ->
        { model with signedInAs = username; signInFailed = Option.isNone username }, onSignIn username
    | SendSignOut ->
        model, Cmd.OfAsync.either remote.signOut () (fun () -> RecvSignOut) Error
    | RecvSignOut ->
        { model with signedInAs = None; signInFailed = false }, Cmd.none

    | Error RemoteUnauthorizedException ->
        { model with error = Some "You have been logged out."; signedInAs = None }, Cmd.none
    | Error exn ->
        { model with error = Some exn.Message }, Cmd.none
    | ClearError ->
        { model with error = None }, Cmd.none


As you can see, the code is too long to manage. if I add more functionality I can barely imagine what the file will be like.

so how can I split the code into different files ?

Archsx
  • 774
  • 1
  • 11
  • 19
  • You can get the basic ideas from samples in Elmish.WPF and from this sample project I made long ago: https://github.com/BentTranberg/ExploreBolero – Bent Tranberg Oct 21 '22 at 04:44
  • @BentTranberg I'm new to Bolero and Blazor and not familiar with some concept under the hood. I've clone your project and try to run it , but I got some error :) . Anyway, I think the docs should have more content about how to organize the project. After check your Main.fs, I still think too much codes integrated in one file . I will learn from your repo anyway. thx – Archsx Oct 21 '22 at 07:17
  • Info: [Elmish](https://elmish.github.io/elmish/) itself, [Elmish.WPF](https://github.com/elmish/Elmish.WPF) with [samples](https://github.com/elmish/Elmish.WPF/tree/master/src/Samples) and [documentation](https://github.com/elmish/Elmish.WPF#recommended-resources), [Bolero](https://fsbolero.io/) of course, and [The Elmish Book](https://zaid-ajaj.github.io/the-elmish-book) by Zaid Ajaj. Also, the issues in the repos contain lots of valuable information, though digging for that can make you feel like you're digging for gold - it's hard work, but can be well worth it. – Bent Tranberg Oct 21 '22 at 15:01
  • 1
    I want to point out the section on [Sub-model bindings](https://github.com/elmish/Elmish.WPF/blob/master/REFERENCE.md#sub-model-bindings) in the [Elmish.WPF reference](https://github.com/elmish/Elmish.WPF/blob/master/REFERENCE.md). That is all about how you scale an Elmish application, or structure it, in various ways. It is specific to Elmish.WPF with regards to the bindings, but generic with regards to the Elmish models. – Bent Tranberg Oct 21 '22 at 15:24

1 Answers1

2

The most basic thing you could do is to split your Message type into multiple separate types and have one corresponding to different aspects of the logic. For example:

type CounterMessage = 
    | Increment
    | Decrement
    | SetCounter of int

type BooksMessage = 
    | GetBooks
    | GotBooks of Book[]

type Message =
    | SetPage of Page
    | CounterMessage of CounterMessage
    | BooksMessage of BooksMessage

Then you can similarly split your update function - and move each of the message-specific functions to a separate file:

let updateCounter remote message model = 
    match message with
    | Increment ->
        { model with counter = model.counter + 1 }, Cmd.none
    | Decrement ->
        { model with counter = model.counter - 1 }, Cmd.none
    | SetCounter value ->
        { model with counter = value }, Cmd.none

let update remote message model =
    match message with
    | SetPage page ->
        { model with page = page }, Cmd.none
    | CounterMessage msg ->
        updateCounter remote message model
    // (...)

This is something using just basic F# langauge mechanisms. Perhaps Bolero has some more sophisticated methods for structuring projects, but this is simple option will always work. The disadvantage is that you need to handle the "forwarding" of the messages yourself (and this is something that a more sophisticated option may alleviate) - but I think that for a moderately sized project, it is not such a bad things (and it helps making things explicit and clear).

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • This is what I looking for! and I think the demo of bolero should be like this! but `type Message = CounterMessage of CounterMessage` seems to be weird with the two same name :) – Archsx Oct 21 '22 at 10:17
  • 1
    @Archsx Yeah - the two names refer to different things (case constructor vs. type name) and it is slightly confusing (but allowed). If you can come up with a better naming scheme, it's probably a good idea to use that instead! – Tomas Petricek Oct 21 '22 at 11:45
  • I'd say Bolero does not have much more sophisticated methods for structuring your Elmish models beyond what Elmish itself offers. The only thing I can think of is the PageModel<'Model> type that will allow you to not have the entire model hierarchy in memory simultaneously, but I see that as an implementation detail that barely crosses the line into Elmish territory. I do wish that Elmish itself could have something similar. – Bent Tranberg Oct 21 '22 at 15:12