I'm trying to parse command line arguments in an F# application. I'm using pattern matching over parameters list to accomplish it. Something like:
let rec parseCmdLnArgs =
function
| [] -> { OutputFile = None ; OtherParam = None }
| "/out" :: fileName :: rest -> let parsedRest = parseCmdLnArgs rest
{ OutputFile = Some(fileName) with parsedRest }
The problem is I want to make "/out"
match case insensitive while preserving the case of other stuff. That means I can't alter the input and match the lowercase version of the input against it (this will lose the fileName
case information).
I have thought about several solutions:
when
clauses which is less than ideal. ToLower "/out"
before every item. Is there a better option/pattern for doing these kind of stuff? I think this is a common problem and there should be a good way to handle it.
SQL pattern matching enables you to use _ to match any single character and % to match an arbitrary number of characters (including zero characters). In MySQL, SQL patterns are case-insensitive by default.
The most basic way to do case insensitive string comparison in JavaScript is using either the toLowerCase() or toUpperCase() method to make sure both strings are either all lowercase or all uppercase.
Using the casefold() method is the strongest and the most aggressive approach to string comparison in Python. It's similar to lower() , but it removes all case distinctions in strings. This is a more efficient way to make case-insensitive comparisons in Python.
Class names and constants are written in uppercase. We can ignore case in Python using the . upper() and . lower() methods.
I quite like your idea of using F# active patterns to solve this. It is a bit more verbose than using pre-processing, but I think it's quite elegant. Also, according to some BCL guidelines, you shouldn't be using ToLower
when comparing strings (ignoring the case). The right approach is to use OrdinalIgnoreCase
flag. You can still define a nice active pattern to do this for you:
open System
let (|InvariantEqual|_|) (str:string) arg =
if String.Compare(str, arg, StringComparison.OrdinalIgnoreCase) = 0
then Some() else None
match "HellO" with
| InvariantEqual "hello" -> printfn "yep!"
| _ -> printfn "Nop!"
You're right that it's more verbose, but it nicely hides the logic and it gives you enough power to use the recommended coding style (I'm not sure how this could be done using pre-processing).
I might do some pre-processing to allow for either "-" or "/" at the beginning of keywords, and to normalize the case:
let normalize (arg:string) =
if arg.[0] = '/' || arg.[0] = '-' then
("-" + arg.[1..].ToLower())
else arg
let normalized = args |> List.map normalize
It's perhaps not ideal, but it's not like any user is going to have enough patience to type so many command-line parameters that looping through them twice is noticeably slow.
You can use guards to match your deal:
let rec parseCmdLnArgs =
function
| [] -> { OutputFile = None ; OtherParam = None }
| root :: fileName :: rest when root.ToUpper() = "/OUT" -> let parsedRest = parseCmdLnArgs rest
{ OutputFile = Some(fileName) with parsedRest }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With