Help Making Better Use of the Type System

I am very much a learner, but I have this brutally simple form. It does the job. Good enough, but I have another very complicated similar form that needs simplifying.

The issue I have is that the lists of strings are really easy for me to modify and maintain as is, if I refactor after the example here I end up with a huge long “type msg” with a match for every option in every drop down, making it hard to keep it all straight (imagine many more drop downs)

GPT4 and Claude3 are useful but not really.

My next step is to take often_list and turn it into a type, without bloating “type msg” but I am unsure how to do this.

Input welcome, just be aware, step by step is the way I do things. I am not asking for everything, just the next most productive step. Bearing in mind I have another more complicated but similar form that I am aiming to refactor, it’s too long at 1300 lines.

Kindness
David

(* file: survey.ml *)
open Fmlib_browser

let often_list =
  [ ""; "daily"; "weekly"; "monthly"; "quarterly"; "biannually"; "annually" ]

let where_list =
  [
    "";
    "town";
    "good tramping tracks";
    "tough tramping tracks";
    "off-track";
    "scrub";
    "the tops";
    "the crags";
    "the summit";
  ]

let what_list =
  [
    "";
    "under 2 kg";
    "2 to 5 kg";
    "5 to 10 kg";
    "10 to 20 kg";
    "20 to 25 kg";
    "+ 25 kg";
  ]

let first_element list = match list with [] -> "" | hd :: tail -> hd

(* Model*)
type state = {
  often : string;
  most_used : string;
  where : string;
  what : string;
  message : string;
}

let init : state =
  {
    often = first_element often_list;
    most_used = "";
    where = first_element where_list;
    what = first_element what_list;
    message = "";
  }

(* Messages *)
type msg =
  | Often of string
  | Most_used of string
  | Where of string
  | What of string
  | Message of string

(* Views*)

let header_view state =
  let open Html in
  let open Attribute in
  let img attrs nodes = node "img" attrs nodes in
  div []
    [
      div [ class_ "grid" ] [ div [] []; img [ src "logo.svg" ] []; div [] [] ];
      p []
        [
          text
            "Please complete this survey. Help design the next Fiordland Pack.";
        ];
      p []
        [
          text
            "This is anonymous. No google analytics. No facebook pixel. \
             No cookies."; (* Plausible is used though *)
        ];
    ]

let see_view state =
  let open Html in
  let open Attribute in
  div []
    [
      li [] [ text "often: "; text state.often ];
      li [] [ text "most used: "; text state.most_used ];
      li [] [ text "where: "; text state.where ];
      li [] [ text "what: "; text state.what ];
      li [] [ text "message: "; text state.message ];
    ]

let view state =
  let open Html in
  let open Attribute in
  let often str = Often str in
  let most_used str = Most_used str in
  let where str = Where str in
  let what str = What str in
  let message str = Message str in

  let section attrs nodes = node "section" attrs nodes in
  let form attrs nodes = node "form" attrs nodes in
  div
    [ class_ "container" ]
    [
      section [ id "header" ] [ header_view state ];
      section
        [ id "main" ]
        [
          form
            [
              attribute "action" "https://formspree.io/f/abcdefg";
              attribute "method" "post";
            ]
            [
              section []
                [
                  label
                    [ attribute "for" "how_often" ]
                    [
                      text "How often do you use a backpack?";
                      select
                        [
                          attribute "type" "text";
                          attribute "name" "how_often";
                          id "how_often";
                          value state.often;
                          on_input often;
                        ]
                        (List.map
                           (fun x -> node "option" [] [ text x ])
                           often_list);
                    ];
                  label
                    [ attribute "for" "where" ]
                    [
                      text "Where do you use your pack most?";
                      select
                        [
                          attribute "type" "text";
                          attribute "name" "where";
                          id "where";
                          value state.where;
                          on_input where;
                        ]
                        (List.map
                           (fun x -> node "option" [] [ text x ])
                           where_list);
                    ];
                  label
                    [ attribute "for" "what" ]
                    [
                      text "What kind of load do you carry?";
                      select
                        [
                          attribute "type" "text";
                          attribute "name" "what";
                          id "what";
                          value state.what;
                          on_input what;
                        ]
                        (List.map
                           (fun x -> node "option" [] [ text x ])
                           what_list);
                    ];
                  label
                    [ attribute "for" "most_used" ]
                    [
                      text "What is your all time most used backpack?";
                      input
                        [
                          attribute "type" "text";
                          attribute "name" "most_used";
                          id "most_used";
                          value state.most_used;
                          on_input most_used;
                        ]
                        [];
                    ];
                  label
                    [ attribute "for" "message" ]
                    [
                      text "What is most important in a backpack?";
                      textarea
                        [
                          attribute "type" "text";
                          attribute "name" "message";
                          id "message";
                          value state.message;
                          on_input message;
                        ]
                        [];
                    ];
                  button [ attribute "type" "submit" ] [ text "Send" ];
                ];
            ];
        ]
      (* ; section [id "see"] [ see_view state ] *);
    ]

(* Update *)
let update (state : state) = function
  | Often str -> { state with often = str }
  | Most_used str -> { state with most_used = str }
  | Where str -> { state with where = str }
  | What str -> { state with what = str }
  | Message str -> { state with message = str }

let _ =
  sandbox
    init
    view
    update

I’m not clear why you get a much longer type msg here.

You will have to translate from the internal enumerated type to a string form for the browser and back. And you’ll have to handle the option that the browser returns rubbish. That is the whole point of making the type more strict.

In the example you want to be looking at the decode_date stuff.

Ok thanks for your input.

David