Question

Je suis en train d'analyser les arguments de ligne de commande dans une application F #. J'utilise la correspondance de modèles sur la liste des paramètres pour l'accomplir. Quelque chose comme:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | "/out" :: fileName :: rest -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }

Le problème est que je veux faire affaire match "/out" insensible tout en préservant le cas d'autres choses. Cela signifie que je ne peux pas modifier l'entrée et correspondre à la version minuscule de l'entrée contre elle (ce qui va perdre l'information de cas fileName).

J'ai pensé à plusieurs solutions:

  • Resort à when clauses qui est moins qu'idéal.
  • Match un tuple à chaque fois, la première serait le paramètre réel (que je vais économiser pour un traitement ultérieur et sera Wildcard correspondre) et la seconde serait la version utilisée dans ces bas de casse appariements. Cela semble pire que la première.
  • Utiliser des modèles actifs, mais qui semble trop bavard. Je vais devoir répéter des choses comme ToLower "/out" avant chaque article.

Y at-il une meilleure option / modèle pour faire ce genre de choses? Je pense que cela est un problème commun et il devrait y avoir une bonne façon de le gérer.

Était-ce utile?

La solution

Je aime bien votre idée d'utiliser F # modèles actifs pour résoudre ce problème. Il est un peu plus bavard que d'utiliser le prétraitement, mais je pense qu'il est tout à fait élégante. En outre, selon quelques lignes directrices de la BCL , vous shouldn » t utiliseront ToLower lorsque l'on compare les chaînes (en ignorant le cas). La bonne approche consiste à utiliser l'indicateur de OrdinalIgnoreCase. Vous pouvez toujours définir un joli modèle actif pour le faire pour vous:

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!"    

Vous avez raison qu'il est plus bavard, mais il cache bien la logique et il vous donne assez de puissance pour utiliser le style de codage recommandé (je ne sais pas comment cela pourrait être fait à l'aide de pré-traitement).

Autres conseils

Je pourrais faire une pré-traitement pour permettre soit « - » ou « / » au début des mots-clés, et de normaliser le cas:

let normalize (arg:string) =
    if arg.[0] = '/' || arg.[0] = '-' then 
        ("-" + arg.[1..].ToLower())
    else arg
let normalized = args |> List.map normalize

Il est peut-être pas idéal, mais il est pas comme tout utilisateur va avoir assez de patience pour saisir autant de paramètres de ligne de commande en boucle à travers eux est deux fois sensiblement lente.

Vous pouvez utiliser des gardes pour correspondre à votre offre:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | root :: fileName :: rest when root.ToUpper() = "/OUT" -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }

rencontré ce à la recherche d'une solution à un problème similaire, et alors que la solution de Tomas travaille pour les chaînes individuelles, il ne permet pas à la version originale de correspondance de motif contre des listes de chaînes. Une version modifiée de son modèle actif permet des listes de correspondance:

let (|InvariantEqual|_|) : string list -> string list -> unit option =
    fun x y ->
        let f : unit option -> string * string -> unit option =
            fun state (x, y) ->
                match state with
                | None -> None
                | Some() ->
                    if x.Equals(y, System.StringComparison.OrdinalIgnoreCase)
                    then Some()
                    else None
        if x.Length <> y.Length then None
        else List.zip x y |> List.fold f (Some())

match ["HeLlO wOrLd"] with
| InvariantEqual ["hello World";"Part Two!"] -> printfn "Bad input"
| InvariantEqual ["hello WORLD"] -> printfn "World says hello"
| _ -> printfn "No match found"

Je n'ai pas été en mesure de savoir comment faire correspondre avec des espaces réservés bien faire | InvariantEqual "/out" :: fileName :: rest -> ... encore, mais si vous connaissez le contenu de la liste, il est une amélioration.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top