2010-12-14 5 views
7

se si specifica il tipo (credo) corretto per una funzione di ordine elevato, il compilatore OCaml rifiuta l'utilizzo di secondo secondo tale funzione.tipo di funzioni di ordine elevato

Il codice

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 

conduce alla seguente messaggio di errore

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

Quindi il primo utilizzo di f sembra definire il tipo del suo primo parametro da int -> string. Potrei capirlo. Ma quello che non capisco è che l'omissione della restrizione di tipo su f risolve il problema.

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

E spostando f di portata globale risolve il problema, anche:

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

Perché è che il primo esempio non compila, mentre quelle successive fanno?

+3

Sono stumped dalla tua domanda, ma un'osservazione che posso fare è che 'f (g: 'a-> string) (v:' a): string = gv' non fa quello che vuoi: il' 'a' non è lo stesso nelle due annotazioni di tipo. Faresti meglio a scrivere let (f: ('a-> string) ->' a -> string) = fun gv -> gv' (che produce lo stesso errore di tipo) se vuoi vincolare 'g' e' v' con lo stesso ''a'. –

+2

Consiglierei di postare questo nella mailing list di OCaml. –

+0

@Pascal: grazie, ho modificato la domanda – copton

risposta

9

Lasciatemi usare un esempio più semplice che illustra il problema.

# 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 è una definizione funzione polimorfica, definita nell'ambito globale. È solo un involucro banale per l'operatore ::. Non sorprende che la definizione funzioni. cons1 è quasi lo stesso di cons0, con la differenza che il suo ambito è limitato all'espressione nel corpo in. Il cambio di scopo sembra innocuo, e abbastanza sicuro, è tipologicamente. cons3 ha di nuovo la stessa funzione, senza annotazioni di tipo, e possiamo usarlo polimorficamente nel corpo in.

Quindi cosa c'è che non va con cons2? Il problema è l'ambito di 'a: è l'intera frase di livello superiore.La semantica della frase che definisce cons2 è

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

Da 'a devono essere compatibili con int (dovuto cons3 1 []) e con bool (a causa di cons3 true [], non v'è alcuna possibilità di istanziazione 'a. Pertanto la frase è mal digitato.

Se vi piace pensare a ML digitazione in termini del suo algoritmo di solito tipo di inferenza, ogni variabile esplicita dell'utente introduce una serie di vincoli nella algoritmo di unificazione. Qui i vincoli sono 'a = "type of the parameter x" e ' = "type of the parameter y". Ma la sco pe di 'a è l'intera frase, non è generalizzata in alcun ambito interno. Quindi, int e bool terminano entrambi unificati per lo 'a non generalizzato.

Versioni recenti OCaml introduce variabili di tipo scoped (come in Niki Yoshiuchi's answer). Lo stesso effetto si sarebbe potuto ottenere con un modulo locale nelle versioni precedenti (≥2.0):

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

(standard MLers nota:. Questo è un posto dove OCaml e SML differiscono)

+0

La cosa interessante è come 'f (x: 'a)' differisce da 'f x', poiché entrambi sembrano avere la stessa firma di tipo.Il fatto che lo scope globale differisca dall'ambito "in" ha un senso, ciò che non è che l'annotazione del tipo in un generico risolve quel generico in un tipo esplicito. Se il comportamento fosse lo stesso per entrambi i parametri annotati e non annotati, sarebbe meno strano. –

+1

@Niki: Il fatto è che non stai annotando il tipo in un generico, stai annotando con una variabile. L'ambito della variabile è l'intera frase. Penso che questo problema sarebbe naturale se dovessi dichiarare variabili di tipo. (Ricorda anche che sono * tipo * variabili, non di tipo schema variabili.) – Gilles

+0

Grazie per questa risposta istruttiva. Ti ricordi quando è stato deciso che l'ambito delle variabili di tipo come ''a' era la frase? Sono quasi sicuro di ricordare un periodo intorno all'anno 2000, quando lo scope era solo il tipo (in quel momento, il mio commento alla domanda sarebbe stato valido. Ora, non lo è, suppongo ...) –

4

Questo è un vero capolavoro e non sarei sorpreso se si tratta di un bug del compilatore. Detto questo, si può fare ciò che si vuole nominando esplicitamente il tipo:

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

Dal manuale: http://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#htoc108

Edit:

questo funziona anche:

let f g v:string = g v in 

che ha il tipo di firma che stai cercando: ('a -> string) -> 'a -> string

Strano che non funzioni quando si annota il tipo degli argomenti.

EDIT:

annotazioni di tipo polimorfici hanno una sintassi speciale:

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

E il tipo è richiesto di essere polimorfico: http://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#toc79

+1

La tua risposta mi chiede se non si tratti di una regressione introdotta in OCaml 3.12 (allo stesso tempo del metodo di elusione, per così dire). Ho solo 3.12.0. Qualcuno ha provato con una versione precedente? –

+1

@Pascal Cuoq: lo stesso con 3.11.2 –

+0

@Pascal Cuoq stesso con 3.10.2 –

-2

Come punto di riferimento , l'equivalente 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 

compila.

+0

Ci si aspetterebbe di sì, dal momento che 'h' e' i' sono entrambi la stessa funzione di battitura a cappello in F #, giusto? – Chuck

+0

No, dal momento che non sono 'inline', l'inferenza del tipo deduce un tipo monomorfico per ogni basato sull'utilizzo. Quindi 'h' è' int -> stringa' e 'i' è' float -> stringa'. (Puoi sempre aggiungere tipi espliciti o lambda per renderlo più ovvio.) Solo 'inline's può avere tipi di cappello; li usa al di fuori di un contesto inline e devono ottenere istanze di tipo "normale" in base all'utilizzo. – Brian