Basic F# console UI program loop

I desperately want to create great and useful programs for myself and others. I am focused on F# and eventually Azure. I want to focus on the “mechanics” of the program first. In doing so, I want to create a super simple “UI”, a basic “do this” and show me the result. Then let me “do that” and show me the result. A console app seems best to me.

Does the following seem to be a good start? Suggestions?
GIST

open System

let printPrompt () = 
    printf "command:>"

let getCommandLineInput _ =  
    printPrompt ()
    System.Console.ReadLine ()

let writeError value =
    printfn "ERROR - No Match\nYou typed: %s" value

let writeMatch value =
    printfn "Matched: %s" value
 
let processCommandLineInput commandLineInput =
    match commandLineInput with
    | "a" -> writeMatch "a" 
    | "b" -> writeMatch "b" 
    | "exit" -> Environment.Exit 55
    | "quit" -> Environment.Exit 55
    | _ -> writeError commandLineInput 
    //change to output a 'command'?

let progLoop () =
    let commandLineInput = Seq.initInfinite getCommandLineInput
    Seq.iter processCommandLineInput commandLineInput
    
[<EntryPoint>]
let main argv = 
    progLoop () 
    0
  1. You probably might consider option to change color of the message depending on its type (Error, General, Success).

Console.BackgroundColor

  1. In case when you have exactly the same action that performed by different patterns (“quit”, “exit”) you can combine them via “|”
| "a" | "b" as v -> writeMatch v
| "exit" | "quit" -> Environment.Exit 55
  1. You can use pipe-forward operator inside of the progLoop:
let progLoop () =
    getCommandLineInput
    |> Seq.initInfinite
    |> Seq.iter processCommandLineInput

Thank you.

  1. The colors is a great idea, that is more fluff than I am willing to invest at this stage :slight_smile: I would expect that to be a future upgrade though.
  2. This is really helpful. I actually had tried "exit" || "quit" and of course that didn’t work. Now I see the correct way. TY.
  3. Seeing this and being able to compare it gives me great insight. Again. Thank you.
    I updated the GIST

So given the updated program:

//Very basic F# command line UI 
//trying to get a global state

open System

let mutable x = "NoUser"

let printPrompt () = 
    printfn "Current State: %s" x
    printf "command:>"

let getCommandLineInput _ =  
    printPrompt ()
    System.Console.ReadLine ()

let writeError value =
    printfn "ERROR - No Match\nYou typed: %s" value

let writeMatch value =
    printfn "Matched: %s" value
 
let processCommandLineInput commandLineInput =
    match commandLineInput with
    | "a" -> writeMatch "a"; x <- "Ivan"
    | "b" -> writeMatch "b"; x <- "Bob" 
    | "c" -> writeMatch "b" 
    | "exit" | "quit" -> Environment.Exit 55
    | _ -> writeError commandLineInput 
    //change to output a 'command'?

//let progLoop () =
//    let commandLineInput = Seq.initInfinite getCommandLineInput
//    Seq.iter processCommandLineInput commandLineInput
    
let progLoop () =
    getCommandLineInput
    |> Seq.initInfinite
    |> Seq.iter processCommandLineInput


[<EntryPoint>]
let main argv = 
    progLoop () 
    0

I am stuck on updating state without the mutable. My thought is I need to pass the current state through the interactions, but I am not getting it figured out. So the process would be program starts, initial state is set. If I do a particular function, it can create a new state (updated) state and pass it along as the new current state for the next iteration. This is like the Elmish idea. I am just not seeing where to pass and pickup the state in each iteration. Any suggestions?

Then I suppose it would be easier to use simple recursive function.

open System

let printPrompt state = 
    printfn "Current State: %s" state
    printf "command:>"

let getCommandLineInput = Console.ReadLine

let writeError value =
    printfn "ERROR - No Match\nYou typed: %s" value

let writeMatch value =
    printfn "Matched: %s" value
 
let processCommandLineInput state commandLineInput =
    match commandLineInput with
    | "a" -> writeMatch "a"; Some "Ivan"
    | "b" -> writeMatch "b"; Some "Bob"
    | "c" -> writeMatch "c"; Some state
    | "exit" | "quit" -> None
    | _ -> writeError commandLineInput; Some state

let rec progLoop state =
    state |> printPrompt
    let updated = 
        getCommandLineInput()
        |> processCommandLineInput state
    match updated with
    | Some newState -> progLoop newState
    | None -> Environment.Exit 55

[<EntryPoint>]
let main argv = 
    "NoUser" |> progLoop 
    0