2010-12-14 69 views
7

si je spécifie le type (je pense) correct pour une fonction d'ordre élevé, le compilateur OCaml rejette l'utilisation seconde de cette fonction.type de fonctions d'ordre élevé

Le code

let foo():string = 
    let f: ('a -> string) -> 'a -> string = fun g v -> g v 
    in let h = string_of_int 
    in let i = string_of_float 
    in let x = f h 23 
    in let y = f i 23.0 
    in x^y 

conduit au message d'erreur suivant

File "test.ml", line 6, characters 14-15: 
Error: This expression has type float -> string 
     but an expression was expected of type int -> string

La première utilisation de f semble fixer le type de son premier paramètre à int -> string. Je pourrais comprendre ça. Mais ce que je ne comprends pas, c'est que l'omission de la restriction de type sur f résout le problème.

let foo():string = 
    let f g v = g v 
    in let h = string_of_int 
    in let i = string_of_float 
    in let x = f h 23 
    in let y = f i 23.0 
    in x^y

et le déplacement f à portée mondiale résout le problème aussi:

let f: ('a -> string) -> 'a -> string = fun g v -> g v 

let foo():string = 
    let h = string_of_int 
    in let i = string_of_float 
    in let x = f h 23 
    in let y = f i 23.0 
    in x^y

Pourquoi est-ce que le premier exemple ne compile pas tandis que les plus plus tard faire?

+3

Je suis perplexe par votre question, mais une remarque que je peux faire est que 'let f (g: 'a-> chaîne) (v:' a): chaîne = gv' ne fait pas ce que vous voulez: le' 'a' n'est pas la même dans les deux annotations de type. Vous feriez mieux d'écrire 'let (f: ('a-> chaîne) ->' a -> chaîne) = fun gv -> gv' (qui produit le même type erreur) si vous voulez contraindre' g' et ' v' avec le même '' a'. –

+2

Je recommanderais de l'afficher sur la liste de diffusion OCaml. –

+0

@Pascal: merci, j'ai édité la question – copton

Répondre

9

Permettez-moi un exemple plus simple qui illustre la question.

# let cons0 (x : 'a) (y : 'a list) = x :: y;; 
val cons0 : 'a -> 'a list -> 'a list = <fun> 
# let cons1 (x : 'a) (y : 'a list) = x :: y in cons1 1 [];; 
- : int list = [1] 
# let cons2 (x : 'a) (y : 'a list) = x :: y in (cons2 1 [], cons2 true []);; 
This expression has type bool but is here used with type int 
# let cons3 x y = x :: y in (cons3 1 [], cons3 true []);; 
- : int list * bool list = ([1], [true]) 

cons0 est une définition de fonction polymorphes, définie à la portée mondiale. C'est juste un emballage trivial à l'opérateur ::. Sans surprise, la définition fonctionne. cons1 est presque le même que cons0, sauf que sa portée est limitée à l'expression dans le corps in. Le changement de portée semble inoffensif, et bien sûr, il typechecks. cons3 est à nouveau la même fonction, sans annotation de type, et nous pouvons l'utiliser de façon polymorphique dans le corps in.

Alors, quel est le problème avec cons2? Le problème est la portée de 'a: c'est la phrase entière de toplevel.La sémantique de la phrase qui définit cons2 est

for some type 'a, (let cons2 (x : 'a) (y : 'a list) = ... in ...) 

Depuis 'a doit être compatible avec int (en raison de cons3 1 []) et bool (en raison de cons3 true [], il n'y a pas instanciation possible de 'a. Par conséquent, l'expression est mal typé.

Si vous aimez penser à taper ML en termes de son algorithme habituel inférence de type, chaque variable explicite de l'utilisateur introduit un ensemble de contraintes dans l'algorithme d'unification. ici, les contraintes sont 'a = "type of the parameter x" et ' = "type of the parameter y". Mais le sco pe de 'a est l'ensemble de la phrase, il n'est pas généralisé dans une portée interne. Ainsi, int et bool finissent tous deux unifiés au non-généralisé 'a.

Versions récentes OCaml introduit des variables de type scoped (comme dans Niki Yoshiuchi's answer). Le même effet aurait pu être atteint avec un module local dans les versions antérieures (de ≥2.0):

let module M = struct 
    let cons2 (x : 'a) (y : 'a list) = x :: y 
    end in 
(M.cons2 1 [], M.cons2 true []);; 

(note standard MLers:. C'est un endroit où OCaml et SML diffèrent)

+0

Ce qui est intéressant, c'est comment 'f (x: 'a)' diffère de 'f x', car les deux semblent avoir la même signature de type.Le fait que la portée globale diffère de la portée «in» a du sens, ce qui n'est pas que l'annotation du type à un générique résout ce générique en un type explicite. Si le comportement était le même pour les paramètres annotés et non annotés, il serait moins bizarre. –

+1

@Niki: Le fait est que vous n'annotez pas le type à un générique, vous annotez avec une variable. La portée de la variable est la phrase entière. Je pense que ce problème serait naturel si vous deviez déclarer des variables de type. (Rappelez-vous aussi qu'il s'agit de variables * type * et non de variables de type.) – Gilles

+0

Merci pour cette réponse instructive. Vous rappelez-vous quand il a été décidé que la portée des variables de type telles que «a» était la phrase? Je suis presque certain que je me souviens d'un moment autour de l'an 2000 où la portée était juste le type (à ce moment-là, mon commentaire à la question aurait été valide.Maintenant, ce n'est pas, je suppose ...) –

4

Ceci est un vrai gratte-tête et je ne serais pas surpris s'il s'agit d'un bug du compilateur. Cela dit, vous pouvez faire ce que vous voulez en nommant explicitement le type:

let f (type t) (g: t->string) (v: t) = g v in 

A partir du manuel: http://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#htoc108

Edit:

Cela fonctionne aussi:

let f g v:string = g v in 

qui a la signature de type que vous recherchez: ('a -> string) -> 'a -> string

Étrange que cela ne fonctionne pas lorsque vous annotez le type des arguments.

EDIT:

annotations de type polymorphes ont une syntaxe particulière:

let f: 'a. ('a -> string)-> 'a -> string = fun g v -> g v in 

Et le type est nécessaire pour être polymorphes: http://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#toc79

+1

Votre réponse me demande si ce n'est pas une régression introduite dans OCaml 3.12 (en même temps que la méthode de contournement, pour ainsi dire). Je n'ai que 3.12.0. Quelqu'un at-il essayé avec une version plus ancienne? –

+1

@Pascal Cuoq: même avec 3.11.2 –

+0

@Pascal Cuoq même avec 3.10.2 –

-2

En tant que point de référence , l'équivalent F #

let foo():string = 
    let f: ('a -> string) -> 'a -> string = fun g v -> g v 
    let h = string 
    let i = string 
    let x = f h 23 
    let y = f i 23.0 
    x^y 

compile.

+0

On s'y attendrait puisque 'h' et' i' sont tous les deux la même fonction tapée par un chapeau dans F #, non? – Chuck

+0

Non, puisqu'ils ne sont pas 'inline', l'inférence de type déduit un type monomorphe pour chacun d'eux en fonction de l'utilisation. Donc 'h' est' int -> string' et 'i' est' float -> string'. (Vous pouvez toujours ajouter des types explicites ou des lambdas pour rendre cela plus évident.) Seul 'inline's peut avoir des types de chapeau; utilisez-les en dehors d'un contexte en ligne, et ils doivent obtenir des instanciations de type 'normales' basées sur l'utilisation. – Brian