4 votes

Méthodes et opérateurs génériques F#

Jusqu'à présent, j'ai été assez impressionné par l'inférence de type dans F#, cependant j'ai trouvé quelque chose qu'il n'a pas vraiment compris :

//First up a simple Vect3 type
type Vect3 = { x:float; y:float; z:float } with 
  static member (/) (v1 : Vect3, s : float) = //divide by scalar, note that float
    {x=v1.x / s; y= v1.y /s; z = v1.z /s}
  static member (-) (v1 : Vect3, v2 : Vect3) = //subtract two Vect3s
    {x=v1.x - v2.x; y= v1.y - v2.y; z=v1.z - v2.z}
  //... other operators...

//this works fine
let floatDiff h (f: float -> float) x = //returns float
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

//as does this
let vectDiff h (f: float -> Vect3) x = //returns Vect3
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

//I'm writing the same code twice so I try and make a generic function:
let genericDiff h (f: float -> 'a) x : 'a = //'a is constrained to a float 
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

Lorsque j'essaye de construire cette dernière fonction, un gribouillis bleu apparaît sous le signe de la division et le compilateur affiche le redoutable avertissement suivant : "This construct causes code to be less generic than indicated by the type annotations" (Cette construction rend le code moins générique qu'indiqué par les annotations de type). La variable de type 'a a été contrainte à être de type 'float'". Je fournis au Vect3 le code approprié / pour la fonction. Pourquoi m'avertit-il ?

6voto

Tomas Petricek Points 118959

Les fonctions génériques standard de .NET ne sont pas assez expressives pour permettre ce type de fonctions génériques. Le problème est que votre code peut fonctionner pour n'importe quel 'a qui supporte l'opérateur de soustraction, mais les génériques .NET ne peuvent pas capturer cette contrainte (ils peuvent capturer les contraintes d'interface, mais pas les contraintes de membre).

Cependant, vous pouvez utiliser F# inline fonctions et paramètres de type résolus de manière statique qui peuvent avoir des contraintes supplémentaires pour les membres. J'ai écrit un article qui fournit quelques détails supplémentaires à propos de ces derniers.

En bref, si vous marquez la fonction comme inline et laisser le compilateur déduire le type, alors vous obtenez (j'ai supprimé la mention explicite du paramètre de type, car cela rend la situation plus délicate) :

> let inline genericDiff h (f: float -> _) x = 
>   ((f (x + h)) - (f (x - h))) / (h * 2.0);;

val inline genericDiff :
  float -> (float ->  ^a) -> float -> float
    when  ^a : (static member ( - ) :  ^a *  ^a -> float)

Le compilateur utilise maintenant ^a au lieu de 'a pour dire que le paramètre est résolu statiquement (lors de l'inlining) et il a ajouté une contrainte disant que ^a doit avoir un membre - qui en prend deux les choses et renvoie float .

Malheureusement, ce n'est pas tout à fait ce que vous souhaitez, car votre - l'opérateur renvoie Vect3 (et non float comme l'a déduit le compilateur). Je pense que le problème est que le compilateur veut que / avec deux arguments de même type (alors que le vôtre est Vect3 * float ). Vous pouvez utiliser des noms d'opérateurs différents (par exemple, /. ):

let inline genericDiff2 h (f: float -> _) x = 
  ((f (x + h)) - (f (x - h))) /. (h * 2.0);;

Dans ce cas, il fonctionnera sur Vect3 (si vous renommez la division scalaire), mais il ne le fera pas facilement travailler sur float (bien qu'il puisse y avoir des astuces qui rendent cela possible). voir cette réponse - bien que je ne considère pas cela comme un F# idiomatique et que j'essaierais probablement de trouver un moyen d'éviter d'avoir à le faire). Serait-il judicieux de fournir une division par éléments et de passer h como Vect3 valeur, peut-être ?

5voto

Daniel Points 29764

Si vous utilisez des nombres génériques pour vos littéraux, cela fonctionne :

let inline genericDiff h f x =
  let one = LanguagePrimitives.GenericOne
  let two = one + one
  ((f (x + h)) - (f (x - h))) / (h * two)

genericDiff 1.0 (fun y -> {x=y; y=y; z=y}) 1.0 //{x = 1.0; y = 1.0; z = 1.0;}

1voto

John Palmer Points 14866

Pour une raison quelconque, le compilateur suppose que la signature de type de la division est

^a*^a -> ^b

alors qu'il devrait être normal qu'il le soit

^a*^c -> ^b

Je crois que c'est ce qu'implique le point 9.7 de l'annexe. spécimen (c'est le cas pour les unités de mesure, mais je ne vois pas pourquoi elles constituent un cas particulier). Si la division pouvait avoir la signature de type souhaitée, on pourrait faire :

let inline genericDiff h (f: float -> ^a) x = 
    let inline sub a b = (^a: (static member (-):^a * ^a-> ^a) (a,b))
    let inline div a b = (^a: (static member (/):^a -> float-> ^c) (a,b))
    div (sub (f (x+h)) (f(x-h))) (h*2.0)

J'ai inclus des éléments implicites sub y div mais ils ne devraient pas être nécessaires - l'idée est de rendre la signature explicite.

Je pense que le fait que cela ne permette pas ^a est en fait un bogue.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X