Simple Password Generator Feedback This is my first major project in F#, looking for any critique on how I can make it more standard or more concise.
Some things I feel like I could have done better are the command flag parsing and the actual crypto random generation itself. I do know that crypto generators are slower than pseudo rng but I figure that a crytpo generator is better for generating something that should be secure.

open System.Security.Cryptography
open System.Text
open System

[&lt;EntryPoint&gt;]
let main argv =
    let rec contains item list : bool =
        match list with
        | [] -&gt; false
        | head::tail -&gt; (head = item) || contains item tail

    let strContainsOnlyNumber (s:string) = System.UInt32.TryParse s |&gt; fst

    let rec buildChars chars (args: string list) : char list =
        let numbers = ['0'..'9']
        let lower = ['a'..'z']
        let upper = ['A'..'Z']
        let special = ['!' .. '/'] @ ['@']
        match args with
        | [] -&gt; lower @ upper @ numbers
        | head::_ when args.Length = 1 &amp;&amp; strContainsOnlyNumber head -&gt; (chars @ lower @ upper @ numbers)
        | head::tail when head = &quot;-l&quot; || head = &quot;--lower&quot; -&gt;if tail.Length &gt; 0 then buildChars (chars @ lower) tail else chars @ lower
        | head::tail when head = &quot;-u&quot; || head = &quot;--upper&quot; -&gt; if tail.Length &gt; 0 then buildChars (chars @ upper) tail else chars @ upper
        | head::tail when head = &quot;-s&quot; || head = &quot;--special&quot; -&gt; if tail.Length &gt; 0 then buildChars (chars @ special) tail else chars @ special
        | head::tail when head = &quot;-n&quot; || head = &quot;--numeric&quot; -&gt; if tail.Length &gt; 0 then buildChars (chars @ numbers) tail else chars @ numbers
        | _::tail when chars.Length = 1 &amp;&amp; tail.Length = 0 -&gt; lower @ upper @ numbers
        | _::tail when chars.Length = 1 -&gt; buildChars chars tail
        | _ -&gt; chars 

    let rec buildString  (bytes: byte list) (chars: char list) (builder: StringBuilder) : string =
        match bytes with
        | [] -&gt; builder.ToString()
        | head::tail -&gt; buildString tail chars &lt;| builder.Append chars.[(int) head % chars.Length]  

    use rng = new RNGCryptoServiceProvider()
    let bytes : byte array = Array.zeroCreate &lt;| if strContainsOnlyNumber(argv.[0]) then Convert.ToInt32 argv.[0] else 16
    rng.GetBytes(bytes)
    let byteList : byte list = bytes |&gt; Array.toList

    let characters = (buildChars [' '] (argv |&gt; Array.toList)).Tail


    let sb = StringBuilder()
    let pass = buildString byteList characters sb

    printfn &quot;%s&quot; &lt;| pass

    0
As a first attempt in F# you have passed the test, but the overall impression is a little messy, while the algorithm seems to (almost) work as expected.

If calling it with an empty argument list - from this:

match args with
        | [] -&gt; lower @ upper @ numbers


in buildChars, I would expect it to produce a 16 chars password from the default settings. But it fails here:

let bytes : byte array = Array.zeroCreate &lt;| if strContainsOnlyNumber(argv.[0]) then Convert.ToInt32 argv.[0] else 16


with an IndexOutOfRangeException


   let rec contains item list : bool =
        match list with
        | [] -&gt; false
        | head::tail -&gt; (head = item) || contains item tail


This is not used, so delete it. If you need a contains function, the List-module has a predefined such ready to use.

Especially buildChars seems overly messy and complicated, and it is not very efficient that the char lists (numbers, lower, etc) are redefined for each recursion. Instead of having buildChars as a rec function you could have an inner recursive function to the matching and then define the char lists out side of that:
let buildChars chars (args: string list) : char list =
    let numbers = ['0'..'9']
    let lower = ['a'..'z']
    let upper = ['A'..'Z']
    let special = ['!' .. '/'] @ ['@']
    let rec listBuilder chars args = 
        match args with
        | [] -&gt; lower @ upper @ numbers
        // ... etc.

    listBuilder chars args

Besides that, I think I would think of another design, if I find my self repeating the almost same code like in this function. List.fold may be a solutions in this case.
Another issue with the function is that if the argument list contains more of the same argument (for instance &quot;-l&quot;, &quot;-l&quot;) it will be included more than once making the result somewhat biased. Maybe consider to reduce the argument list to a distinct set - unless you want the behavior.

You could consider to print help/information, if the argv has a &quot;-?&quot; entry.

In F#, lists are very convenient because of its operators that makes the code more readable, but in this particular algorithm, I think I would stick to use arrays for everything, because you address the list entries by index, which is not efficient for lists, because chars.[index] is an O(index) operation where the same operation is O(1) for arrays, further List.length is a O(n) operation - adding more inefficiency to the equation.


let rec buildString  (bytes: byte list) (chars: char list) (builder: StringBuilder) : string =
    match bytes with
    | [] -&gt; builder.ToString()
    | head::tail -&gt; buildString tail chars &lt;| builder.Append chars.[(int) head % chars.Length]


This function is not tail recursive and therefore builds up the stack. For a password generator it will probably never be an issue, but there is a hypothetical risk for a stack overflow. Fortunately you can easily make it tail recursive and more efficient, because builder.Append returns the builder itself. So changing the last line to
| head::tail -&gt; buildString tail chars (builder.Append chars.[(int) head % chars.Length])

makes the function tail recursive.

Below is my version with some inline explanation:
let passwordGenerator (argv: string []) = 

    // The @ - operator for lists is temporarily redefined to work with arrays
    // in order to make the code more readable
    let inline (@) left right = Array.append left right

    // From the first argument or a possible empty argument list the
    // custom size and if the default settings should be used is determined
    let useDefaults, size = 
        match argv.Length with
        | 0 -&gt; true, 16
        | _ -&gt; 
            match (Int32.TryParse(argv.[0])) with
            | true, n -&gt; (argv.Length = 1), n
            | false, _ -&gt; false, 16

    // The usable characters are determined from the arguments
    let chars = 
        let lower = [| 'a'..'z' |]
        let upper = [| 'A'..'Z' |]
        let numbers = [| '0'..'9' |]
        let special = [| '!' .. '/' |] @ [| '@' |]

        if useDefaults then
            lower @ upper @ numbers
        else
            // This will avoid duplicate chars
            let predicate arg short long (chs: char[]) all = 
                (arg = short || arg = long) &amp;&amp; not (all |&gt; Array.contains (chs.[0]))

            let folder all arg =
                match arg with
                | a when predicate a &quot;-l&quot; &quot;--lower&quot; lower all -&gt; all @ lower
                | a when predicate a &quot;-u&quot; &quot;--upper&quot; upper all -&gt; all @ upper
                | a when predicate a &quot;-n&quot; &quot;--numerics&quot; numbers all -&gt; all @ numbers
                | a when predicate a &quot;-s&quot; &quot;--special&quot; special all -&gt; all @ special
                | _ -&gt; all

            argv |&gt; Array.fold folder [||]

    // Provides the random bytes
    let bytes = 
        use rng = new RNGCryptoServiceProvider()
        let bytes = Array.zeroCreate size
        rng.GetBytes(bytes)
        bytes

    // Generates the password
    let password = 
        bytes 
        |&gt; Array.map (fun b -&gt; chars.[int b % chars.Length]) 
        |&gt; fun chs -&gt; new String(chs)

    printfn &quot;%s&quot; password