2016-06-22 3 views
4

Voici un exemple de code minimal que j'utilise pour expliquer mon problème. Le code suivant est organisé en deux fichiers et compile bien:Comment F # infère-t-il les types et les balises des autres modules?

DataStruct.fs

module MyMod 
type XXX = { 
    a: int 
} 
with 
    static member GetNew = 
     { 
      a = -1 
     } 

type YYY = { 
    a: float 
} 
with 
    static member GetNew = 
     { 
      a = -1.0 
     } 


type Choice = 
    | XXX of XXX 
    | YYY of YYY 

Program.fs

open MyMod 

let generator = 
    let res = XXX.GetNew 
    Choice.XXX res 

let myVal : XXX = 
    match generator with 
    | XXX x -> x 
    | _ -> printfn "expected XXX, got sth else!"; XXX.GetNew 

La chose intéressante est que j'ai un type de choix qui a deux balises nommé de la même manière que les types qu'ils marquent. C'est, d'après ce que je comprends, une convention commune en F #.

Maintenant, je modifie DataStruct de telle sorte que je l'ai placé dans un espace de noms et que MyMod soit l'un des modules de cet espace de noms. En conséquence, dans Program.fs j'ouvre l'espace de noms et d'utiliser tout préfixé avec le nom du module:

DataStruct.fs

namespace DataStruct 

module MyMod = 
    type XXX = { 
     a: int 
    } 
    with 
     static member GetNew = 
      { 
       a = -1 
      } 

    type YYY = { 
     a: float 
    } 
    with 
     static member GetNew = 
      { 
       a = -1.0 
      } 


    type Choice = 
     | XXX of XXX 
     | YYY of YYY 

Program.fs

open DataStruct 

let generator = 
    let res = MyMod.XXX.GetNew 
    MyMod.Choice.XXX res 

let myVal : MyMod.XXX = 
    match generator with 
    | MyMod.XXX x -> x 
    | _ -> printfn "expected XXX, got sth else!"; MyMod.XXX.GetNew 

maintenant Program.fs contient deux erreurs . Dans les deux lignes où j'essaie d'appeler GetNew, il est écrit: "Le champ, le constructeur ou le membre 'GetNew' n'est pas défini" Ceci est dû au fait que MyMod.XXX est inféré comme étant un cas de type MyMod.Choice. Maintenant, sans changer la structure de mon code, je renomme simplement les tags Choice pour qu'ils soient différents des types qu'ils représentent et tout fonctionne correctement.

DataStruct.fs comme ci-dessus, mais avec

type Choice = 
    | TX of XXX 
    | TY of YYY 

Program.fs

open DataStruct 

let generator = 
    let res = MyMod.XXX.GetNew 
    MyMod.Choice.TX res 

let myVal : MyMod.XXX = 
    match generator with 
    | MyMod.TX x -> x 
    | _ -> printfn "expected XXX, got sth else!"; MyMod.XXX.GetNew 

Maintenant, l'appel à GetNew est légal puisque le MyMod.XXX est déduit correctement le type que je voulais utilisation.

La question est maintenant: le problème décrit ci-dessus est-il un bug ou une caractéristique de F #? Autrement dit, s'il est conseillé d'utiliser les mêmes noms pour les étiquettes et leurs types, cela semble être un problème pour le mécanisme d'inférence de type. Est-ce que le conseil est mauvais ou est-ce que j'utilise des espaces de noms, des modules, des types et des étiquettes dans un mauvais chemin?

Répondre

1

Ce comportement est un bogue dans le compilateur. Le problème est lié à un autre bogue dans le compilateur, où les types d'union discriminés observent d'autres définitions de type dans le même module, see this bug report. Dans le code que vous avez posté: La cause du bug est here dans la résolution du nom. MyMod.XXX est identifié comme une expression valide qui fait référence à un type DU. Cette recherche est faite avec avidité, le codepath that searches for alternative resolutions n'est pas exécuté.

J'ai présenté des rapports de bogues in visualfsharp et fsharp

+0

Merci pour l'effort, j'espère que ce sera corrigé bientôt. Et je suis un peu surpris qu'il n'y ait pas eu beaucoup de gens qui ont trébuché sur ce sujet, car une telle utilisation d'une union discriminée apparaîtra fréquemment dans F #, je crois. –

+0

@FriedrichGretz: merci pour la confirmation. Satisfaisant pour la prime? –

5

La différence entre votre premier et le deuxième morceau de code est la façon dont vous ouvrez le module dans Program.fs:

  • Dans votre premier code, en écrivant open MyMod, vous ouvrez le module

  • Dans votre deuxième version, en écrivant open DataStruct, vous ouvrez uniquement l'espace de noms, mais pas encore le module. Si vous changez cela en open DataStruct.MyMod, vous obtiendrez exactement le même comportement que dans la première version.

Mon explication approximative de ce qui se passe:

  • Avec le module ouvert, F # voit deux XXX flottant autour, et est en mesure de lever l'ambiguïté sur l'usage.
  • Lorsque vous êtes qualifié avec le nom du module, vous restreignez XXX au dernier type XXX défini dans MyMod. Le premier XXX est votre enregistrement, le second est une classe dérivée de Choice qui est également appelée XXX. Jetez un oeil à votre assemblée dans quelque chose comme ILSpy, par exemple.

MISE À JOUR: Le deuxième paragraphe était incorrect. Une fois qualifié avec le nom de module, le compilateur F # restreint incorrectement XXX pour être un type DU, ombrant le type d'enregistrement. Voir ma deuxième réponse pour plus de détails.

+0

En ce qui concerne le deuxième point de votre explication: dans la ligne « laisser myVal: MyMod.XXX » XXX est correctement reconnu que le type qui contredit que seul le plus tard déclaré MyMod.Choice.XXX est visible en tant que XXX. –

+0

Vous avez raison, c'est incompatible avec mon explication. Laissez-moi creuser un peu plus loin. –