4
votes

I want to use the following code to restrict there is only one argument. However, I got the following error at first :: NIL?

Error   1   This expression was expected to have type
    string []    
but here has type
    'a list 
[<EntryPoint>]
let main argv = 

    match argv with 
    | first :: NIL -> 
         .... do something with first
    | _ -> failwith "Must have only one argument."
3
If you want a 3rd party lib, I like using UnionArgParser for F# cmd line tools. - Leaf Garland

3 Answers

8
votes

The command line arguments are passed as an array, not a list.

Do something like this if you expect exactly one argument:

match argv with 
| [|first|] -> 
     // .... do something with first
| _ -> failwith "Must have only one argument."
4
votes

As mentioned in the accepted answer the "args" argument to the entrypoint is an array, not a list, so you cannot use it with the syntax for list matching.

Instead of matching on the array, as suggested above, you could turn the arguments into an actual list and use that for matching. I have found that a very useful way to handlie command line arguments (though it may be overkill for your example case). As an example:

[<EntryPoint>]
let main args =
  let arglist = args |> List.ofSeq
  match arglist with
  | first :: [] ->
    // do something with 'first'
  | _ -> // catches both the no-argument and multi-argument cases
    printfn "Usage : "
    // print usage message

Edit: As for more complicated examples there are two ways to go from here. You can of course add more complicated cases in the match, or you could parse the list of arguments in a recursive way to build an object representing options and arguments. The latter would get a bit too complicated to fit here, but as an example of some more complex match cases, here is some code related to some recent work where the executable accepts a "command" to operate on a target file, and each command has different further arguments (each command calls a function whose implementation I left out for sake of brevity)

[<EntryPoint>]
let main args =
  let arglist = args |> List.ofSeq
  match arglist with
  | target :: "list" :: [] ->
    listContent target
  | target :: "remove" :: name :: [] ->
    removeContent target name
  | target :: "add" :: name :: [] ->
    addContent target name
  | target :: "addall" :: names ->
    for name in names do
      addContent target name
  | _ -> // catches cases not covered above
    printfn "Usage : "
    // print usage message
0
votes

How about Active Patterns for parsing individual commands, returning an Option indicating None on failure and Some with the content of recovered parameters, possibly nestled in their own dedicated type. Converting "argv" to "string list" is merely a convenience act due to the convenience of list syntax in F#. Note: there are a lot of type annotations here that are typically unnecessary.

type Cmd1Parms = ....
type Cmd2Parms = ....

let performCmd1 cmd1Parms = ...
let performCmd2 cmd2Parms = ...
let commandNotFound argL  = ...

let (|ParseForCmd1|_|) argL : Cmd1Parms option = ....
let (|ParseForCmd2|_|) argL : Cmd2Parms option = ....

[<EntryPoint>]
let main argv =
   let argL = List.ofSeq<string> argv
   match argL with
   | ParseForCmd1 cmd1Parms -> performCmd1 cmd1Parms
   | ParseForCmd2 cmd2Parms -> performCmd2 cmd2Parms
   | _                      -> commandNotFound argL