2009-02-01 8 views
43

J'essaie de comprendre comment définir une fonction qui fonctionne sur plusieurs types de paramètres (par exemple int et int64). Si je comprends bien, la surcharge de fonction n'est pas possible en F # (certainement le compilateur se plaint). Prenons par exemple la fonction suivante.Fonctions avec types de paramètres génériques

let sqrt_int = function 
    | n:int -> int (sqrt (float n)) 
    | n:int64 -> int64 (sqrt (float n)) 

Le compilateur bien sûr se plaint que la syntaxe est invalide (des contraintes de type en correspondance de motif ne sont pas pris en charge il semble), mais je pense que cela illustre ce que je voudrais obtenir: une fonction qui fonctionne sur plusieurs types de paramètres et renvoie une valeur du type correspondant. J'ai le sentiment que cela est possible en F # en utilisant une combinaison de types génériques/type d'inférence/correspondance de formes, mais la syntaxe m'a échappé. J'ai aussi essayé d'utiliser le:? opérateur (tests de type dynamique) et lorsque clauses dans le bloc de correspondance de modèle, mais cela produit toujours toutes les erreurs de tris. Comme je suis plutôt novice en la matière, il se peut très bien que j'essaie de faire quelque chose d'impossible ici, alors s'il vous plaît faites le moi savoir s'il y a une solution alternative.

Répondre

58

La surcharge est typiquement le bugaboo des langages à inférence de type (au moins quand, comme F #, le système de type n'est pas assez puissant pour contenir des classes-types). Il y a un certain nombre de choix que vous avez en F #:

  • utiliser la surcharge sur les méthodes (membres d'un type), dans lequel fonctionne une surcharge cas tout comme dans d'autres langues .Net (vous pouvez ad-hoc membres de surcharge, les appels fournis peuvent être distingués par le nombre/type de paramètres)
  • Utilisez les contraintes de membre "inline", "^" et statiques pour les surcharges ponctuelles sur les fonctions (c'est ce que la plupart des différents opérateurs mathématiques qui doivent travailler sur int/float/etc; la syntaxe ici est bizarre, elle est peu utilisée en dehors de la bibliothèque F #)
  • Simule des classes de type en transmettant un paramètre supplémentaire de dictionnaire d'opérations (c'est ce que fait INumeric dans l'une des classes). le F # PowerPack li braries à généralisent différents algorithmes mathématiques pour les types définis par l'utilisateur arbitraires)
  • revenir à typage dynamique (passer un paramètre « obj », faire un test de type dynamique, lancer une exception d'exécution pour le type mauvais)

pour votre exemple particulier, je voudrais utiliser probablement la surcharge de méthode:

type MathOps = 
    static member sqrt_int(x:int) = x |> float |> sqrt |> int 
    static member sqrt_int(x:int64) = x |> float |> sqrt |> int64 

let x = MathOps.sqrt_int 9 
let y = MathOps.sqrt_int 100L 
+0

Bonne clarification - Je pense que je comprends ce qui se passe maintenant.Merci pour cela.Après avoir lu sur les choses, Il semble que F # manque encore quelques-unes des caractéristiques fonctionnelles nice languages comme peut l'être Haskell, peut-être que ces fonctionnalités seront bientôt implémentées puisque F # est un langage .NET de 1ère classe – Noldorin

+0

ne sait pas si vous voulez que ce soit une question 'vivante' ou pas, mais puisque la version actuelle de f # n'a plus besoin de OverloadId attrib (! yey) vous pouvez régler la réponse ... – ShuggyCoUk

+0

Merci, je l'avais déjà mis à jour le code, mais il a oublié de mettre à jour la prose – Brian

14

Oui, cela peut être fait. Jetez un oeil à this hubFS thread.

Dans ce cas, la solution serait:

let inline retype (x:'a) : 'b = (# "" x : 'b #) 
let inline sqrt_int (n:'a) = retype (sqrt (float n)) : 'a 

caveat: aucune vérification de type compilation. C'est à dire. sqrt_int "blabla" se compile bien mais vous obtiendrez une exception FormatException lors de l'exécution.

+0

Merci, qui semble être la solution (bien que ce n'est pas aussi simple que je pourrais avoir espéré). Juste pour clarifier, je voudrais quelque chose comme ça? nous sqrt_int en ligne (n:^a) = retapez (sqrt (float n)):^un – Noldorin

+1

Eh oui, qui fonctionne. Cependant, sachez qu'avec cela vous perdez la vérification de type à la compilation. C'est à dire. sqrt_int "blabla" vérifie le type même si vous obtenez une exception FormatException à l'exécution. –

+0

Ok, donc il n'y a vraiment aucune raison d'utiliser l'opérateur de chapeau dans ce cas, non? S'il m'arrivait d'utiliser un opérateur arithmétique tel que * dans la fonction (avant la distribution), cela assurerait-il la vérification de la compilation? – Noldorin

2

ne pas enlever des bonnes réponses déjà fournies, mais vous pouvez en fait des contraintes de type d'utilisation en correspondance de motif. La syntaxe est:

| :? type -> 

Ou si vous voulez combiner la vérification de type et la coulée:

| :? type as foo -> 
+0

C'est ce que j'ai d'abord cru pouvoir faire. Malheureusement, il donne une erreur "coercion d'exécution" (erreur FS0008). Avec la fonction retype fournie dans un lien dans le post de mausch, il devrait cependant fonctionner comme une alternative au mot-clé inline si je le comprends correctement. – Noldorin

+0

la contrainte d'exécution peut être évitée en encadrant la variable dont le type est mis en correspondance en utilisant 'match box variable with' – Remko

9

Voici une autre façon en utilisant les contrôles de type d'exécution ...

let sqrt_int<'a> (x:'a) : 'a = // ' 
    match box x with 
    | :? int as i -> downcast (i |> float |> sqrt |> int |> box) 
    | :? int64 as i -> downcast (i |> float |> sqrt |> int64 |> box) 
    | _ -> failwith "boo" 

let a = sqrt_int 9 
let b = sqrt_int 100L 
let c = sqrt_int "foo" // boom 
+0

Intéressant. Maintenant j'ai trop d'options! Une question: pourquoi vous avez besoin du spécificateur générique <'a> lorsque vous spécifiez une contrainte de type sur x. Je pensais qu'ils étaient des syntaxes équivalentes. – Noldorin

+0

Vous pouvez en effet omettre le <'a> (essayez-le). Notez la différence de perf potentiel; la surcharge de méthode détermine quelle version à la compilation, alors que cette version effectue une vérification de type à l'exécution. (Il se peut que 'sqrt' submerge ces considérations, cependant, je n'ai pas mesuré.) – Brian

+0

Et bien sûr l'autre différence est que cette version compile (et se lance à l'exécution) pour les non-ints, alors que la version surchargée de méthode erreur de compilation pour les non-ints. – Brian

13

Cela fonctionne:

type T = T with 
    static member ($) (T, n:int ) = int (sqrt (float n)) 
    static member ($) (T, n:int64) = int64 (sqrt (float n)) 

let inline sqrt_int (x:'t) :'t = T $ x 

Il utilise des contraintes statiques et la surcharge, ce qui en fait une recherche de compilation sur le type de l'argument.

Les contraintes statiques sont générées automatiquement en présence d'un opérateur (opérateur $ dans ce cas), mais il peut toujours être écrit à la main:

type T = T with 
    static member Sqr (T, n:int ) = int (sqrt (float n)) 
    static member Sqr (T, n:int64) = int64 (sqrt (float n)) 

let inline sqrt_int (x:'N) :'N = ((^T or ^N) : (static member Sqr: ^T * ^N -> _) T, x) 

En savoir plus sur cette here.

+1

C'est une très belle amélioration par rapport à la solution fournie par Brian et Mauricio, elle a l'avantage d'une fonction sans notation (comparez celle de Brian) et elle ajoute une vérification de type à la compilation (comparez Mauricio). Souhaitez-vous préciser dans votre réponse comment cela fonctionne et si la définition de l'opérateur est une exigence? – Abel

+2

Merci @Abel, j'ai développé un peu plus sur la solution et inclus un lien vers une entrée de blog avec plus de détails. La réponse de Mauricio adopte une approche très différente qui est également valide, elle utilise toujours le même code pour tous les types en utilisant to '' float'' qui est moins de code mais si vous voulez travailler avec de grands entiers, vous pouvez rencontrer une limitation . – Gustavo

Questions connexes