let rec nth (list, i) =
match list, i with
| x::xs, 0 -> x
| x::xs, i -> nth(xs, i-1)
| [], _ -> ()
Cette fonction aura en effet la (indésirable) la signature que vous avez mentionné:
val nth : unit list * int -> unit
Pourquoi? Regardez le côté droit de ces trois règles. Si ce n'était pas le ()
, vous ne pourriez pas dire quel type de valeur concrète votre fonction renvoie. Mais dès que vous ajoutez la dernière règle, F # voit l'expression ()
(qui a le type unit
) et de cela peut déduire le type de retour de votre fonction; qui n'est plus générique maintenant. Comme toute fonction ne peut avoir qu'un type de retour fixe, elle en déduit que x
, xs
impliquent également le type unit
, ce qui donne la signature ci-dessus. Comme déjà noté par kvb, vous voulez parfois retourner une valeur, et dans la distribution d'une liste d'entrée vide, vous ne voulez rien renvoyer ... ce qui signifie que votre valeur de retour doit être 'a option
(peut également être . écrit option<'a>
BTW)
let rec nth (list, i) =
match list, i with
| x::xs, 0 -> Some(x)
| x::xs, i -> nth(xs, i-1) // <-- nth already returns an 'a option,
| [], _ -> None // no need to "wrap" it once more
maintenant la signature rapporté semble correct:
val nth : 'a list * int -> 'a option
concernant votre se Question de cond, j'avoue que je ne peux pas y répondre complètement parce que je suis toujours un débutant F # moi-même. Un conseil, cependant: Si vous prenez la fonction ci-dessus dans sa forme correcte (la version générique renvoyant un 'a option
), vous ne pouvez pas vous empêcher de vérifier toutes les valeurs possibles:
Pourquoi? Parce que si vous voulez arriver à la valeur de rendement réel (du nom x
dans le code juste montré), vous devez « extraire » à l'aide d'un bloc match
:
let result = nth (someList, someIndex)
match result with
| Some(x) -> ...
| None -> ...
Et puisque vos règles devrait toujours être exhaustive (de peur le compilateur se plaindre), vous devrez automatiquement ajouter une règle qui vérifie la possibilité de None
.
Le compilateur va effectivement forcer à considérer également ce qui devrait arriver dans une situation "d'erreur"; vous n'êtes pas autorisé à l'oublier. Vous avez seulement le choix de comment faire face à cela!
(Bien sûr, dès que vous faire de la programmation .NET et doivent faire face à des types qui peuvent être null
, les choses peut sembler un peu différent, puisque null
n'est pas un concept natif de F #.)
Autre suggestion d'amélioration: Si vous changez la façon dont votre fonction nth
accepte ses arguments, vous avez la possibilité de l'appliquer partiellement, ce qui signifie par exempleque vous pouvez l'utiliser avec l'opérateur pipelining:
let rec nth i list = // <-- swap order of arguments, don't pass them in
... // as a tuple but as two separate arguments
Maintenant, vous pouvez faire ceci:
someList
|> nth someIndex
Ou ceci:
let third = nth 2
someList |> third
Si, d'autre part, votre fonction ne accepte un tuple, cela ne fonctionnera pas. Donc, considérez si vous avez vraiment besoin d'un paramètre de tuple: Dans ce cas, il restreint réellement la flexibilité, et plus encore, le "sens"/contenu des deux paramètres ne suggère pas qu'ils doivent toujours être conservés et apparaître ensemble. Je déconseille donc d'utiliser un tuple dans ce cas.
"La rigueur est proportionnelle au nombre de développeurs qui pourraient appeler votre code" Résumé génial. – TechNeilogy
Cela peut sembler un peu banal, mais vous ne pouvez jamais vous tromper avec un code "solide comme le roc". Après tout, ce n'est pas souvent que vous pouvez garantir la façon dont votre code sera utilisé, ou par qui, dans le futur. – Daniel
@Daniel, Vrai, mais il y a un coût associé à cette décision, tant pour le temps de développement que pour la maintenance. Si chaque méthode devait être aussi paranoïaque, vous obtiendriez une base de code pleine de rebondissements, ainsi que des développeurs non inspirés. –