Inferenza dei tipi (prog funzionale)
Salve, ho un nuovo problema da sottoporvi... Mi sto preparando per l'esame di Programmazione Funzionale, che consiste in esercizi di vario gente, in particolare dove bisogna determinare l'inferenza di tipo di alcune funzioni (ovviamente senza pc XD)... Si parla di linguaggio OCaml. Allora, per alcune funzioni non ho problemi, per esempio:
devo passare in input un intero u ed una lista di interi, ottenendo una lista di interi quindi: int->int list->int list list
Ma ci sono alcune che non riesco proprio a capire da che parte devo cominciare a determinarne il tipo... per esempio:
(sol f: 'a ->'b list -> ('b*'a) list )
Altri esempi:
List.map print_string;;
List.iter print_int;;
fun x y -> x y;; (questa non riesco proprio a capire cosa dovrei fare!)
Ma da dove dovrei partire esattamente? Qualche trucco/consiglio/dritta per determinare questi tipi senza diventare matti (e soprattutto, senza imparare a memoria)?
Grazie in anticipo
let rec f u= function []->[] |x::xs->[(u*x)]::(f u xs);;
devo passare in input un intero u ed una lista di interi, ottenendo una lista di interi quindi: int->int list->int list list
Ma ci sono alcune che non riesco proprio a capire da che parte devo cominciare a determinarne il tipo... per esempio:
let f y lst= List.map (fun x->x,y) lst;;
(sol f: 'a ->'b list -> ('b*'a) list )
Altri esempi:
List.map print_string;;
List.iter print_int;;
fun x y -> x y;; (questa non riesco proprio a capire cosa dovrei fare!)
Ma da dove dovrei partire esattamente? Qualche trucco/consiglio/dritta per determinare questi tipi senza diventare matti (e soprattutto, senza imparare a memoria)?
Grazie in anticipo

Risposte
Ciao,
bhe un "trucco" è pensare alla composizone di funzioni.
Conosci il tipo più interno alla funzione e lo applichi al tipo funzione più esterno. Se è un $alpha$-tipo allora lo restringi al tipo corretto, e così via di applicazioni.
Se hai dei moduli (List, Hash, ecc) è bene conoscerne il tipo delle loro funzioni, così non sbagli.
Es:
List.map print_string;;
List.map
('a -> 'b) -> 'a list -> 'b list
print_string
string -> unit
List.map(print_string): applicazione di una funzione parziale
('a -> 'b) che è input funzione, viene sostituito dalla funzione completa: string -> unit
'a list: lo specializzi con l'input della funzione completa print_string: string
'b list: lo specializzi con l'output della funzione completa print_string: unit
perciò la funziona parziale risultante: string list -> unit list =
se hai dubbi chiedi pure
bhe un "trucco" è pensare alla composizone di funzioni.
Conosci il tipo più interno alla funzione e lo applichi al tipo funzione più esterno. Se è un $alpha$-tipo allora lo restringi al tipo corretto, e così via di applicazioni.
Se hai dei moduli (List, Hash, ecc) è bene conoscerne il tipo delle loro funzioni, così non sbagli.
Es:
List.map print_string;;
List.map
('a -> 'b) -> 'a list -> 'b list
print_string
string -> unit
List.map(print_string): applicazione di una funzione parziale
('a -> 'b) che è input funzione, viene sostituito dalla funzione completa: string -> unit
'a list: lo specializzi con l'input della funzione completa print_string: string
'b list: lo specializzi con l'output della funzione completa print_string: unit
perciò la funziona parziale risultante: string list -> unit list =
se hai dubbi chiedi pure

Ma in realtà l'input di List.map non dovrebbe essere (a'->'b)->a' list, visto che prende in pasto una funzione f e una lista? Se ho capito bene (a'->'b) sta per la funzione f che poi si deve applicare alla lista, giusto?
Sì esatto l'input di List.map è (a'->'b)->a' list che darà un output di 'b list.
Cioè prende una funzione ed una lista, e applicando la funzione alla lista, otterrai un output conforme all'output della funzione entrante.
Cioè prende una funzione ed una lista, e applicando la funzione alla lista, otterrai un output conforme all'output della funzione entrante.
let f y lst=
List.map (fun x->x,y) lst;;
Allora, vediamo se ho capito... L'input di List.map è (a'->'b)->'a list ed in output ha una ->'b list...
let f y lst ha come input un 'a->'a list
fun x->x,y dovrebbe essere allora 'a->('a*'b)
Quindi sostituisco l'input di fun x->x,y al posto di 'a list ottenendo 'a, poi al posto di 'b list devo sostituire ('a*'b):
'a->('a*'b) list.... mi manca un pezzo, ma non ho capito dove devo infilare quella parte scritta in grassetto... la soluzione infatti dovrebbe essere questa (sol f: 'a ->'b list -> ('b*'a) list )
List.map (fun x->x,y) lst;;
Allora, vediamo se ho capito... L'input di List.map è (a'->'b)->'a list ed in output ha una ->'b list...
let f y lst ha come input un 'a->'a list
fun x->x,y dovrebbe essere allora 'a->('a*'b)
Quindi sostituisco l'input di fun x->x,y al posto di 'a list ottenendo 'a, poi al posto di 'b list devo sostituire ('a*'b):
'a->('a*'b) list.... mi manca un pezzo, ma non ho capito dove devo infilare quella parte scritta in grassetto... la soluzione infatti dovrebbe essere questa (sol f: 'a ->'b list -> ('b*'a) list )
Non ti conviene pensare a input e output in quel modo. Consideriamo List.map. Il suo tipo è ('a -> 'b) -> 'a list -> 'b list, che puoi riscrivere nel modo seguente: ('a-> 'b) -> ('a list -> 'b list). In altre parole è una funzione che data una funzione da 'a a 'b "restituisce" una funzione che data una lista di 'a restituisce una lista di 'b. In questo modo diventa tutto più facile perché ogni funzione ha un solo argomento. Interpretando le cose in questo modo la tua funzione hai che:
- f y ha un argomento di tipo 'a (perché non è limitato in alcun modo dal corpo della funzione) e restituisce una funzione che prende in argomento una lista i cui elementi hanno un altro tipo 'b. Questa funzione restituisce ora un elemento che ha il tipo dell'istruzione List.map (fun x->x,y) lst;; List.map richiede come argomento una funzione come abbiamo visto e questa funzione ha tipo ('b -> 'b*'a) e resituisce una funzione che presa una lista di 'b restituisce una lista di 'b*'a. A questa funzione abbiamo passato la lista e quindi rimane che la funzione ha tipo 'a -> 'b list -> ('b*'a) list. Forse non è il massimo della comprensione.. Ma ti consiglio in ogni caso di vedere un pezzo per volta in questo modo.. Parti cioè ad esempio da
List.map : ('a -> 'b) -> ('a list -> 'b list)
(fun x -> x, y) : ('a -> 'a * 'b)
Quindi sostituendo uno nell'altro e aggiustando i tipi in modo opportuno
List.map (fun x -> x,y) : 'a list -> 'a*'b list
lst : 'a list
List.map (fun x -> x,y) lst : 'a * 'b list
A questo punto sai che y ha tipo 'b e lst l'hai calcolato sopra per cui il tipo di f diventa:
f: 'a -> 'b list -> ('a*'b) list
- f y ha un argomento di tipo 'a (perché non è limitato in alcun modo dal corpo della funzione) e restituisce una funzione che prende in argomento una lista i cui elementi hanno un altro tipo 'b. Questa funzione restituisce ora un elemento che ha il tipo dell'istruzione List.map (fun x->x,y) lst;; List.map richiede come argomento una funzione come abbiamo visto e questa funzione ha tipo ('b -> 'b*'a) e resituisce una funzione che presa una lista di 'b restituisce una lista di 'b*'a. A questa funzione abbiamo passato la lista e quindi rimane che la funzione ha tipo 'a -> 'b list -> ('b*'a) list. Forse non è il massimo della comprensione.. Ma ti consiglio in ogni caso di vedere un pezzo per volta in questo modo.. Parti cioè ad esempio da
List.map : ('a -> 'b) -> ('a list -> 'b list)
(fun x -> x, y) : ('a -> 'a * 'b)
Quindi sostituendo uno nell'altro e aggiustando i tipi in modo opportuno
List.map (fun x -> x,y) : 'a list -> 'a*'b list
lst : 'a list
List.map (fun x -> x,y) lst : 'a * 'b list
A questo punto sai che y ha tipo 'b e lst l'hai calcolato sopra per cui il tipo di f diventa:
f: 'a -> 'b list -> ('a*'b) list
piccola nota a ciò che dice apatriarca, che sì, la parentesizzazione a sinistra dei tipi è un modo per semplificarsi la vita 
comunque in: fun x -> (x,y)
attenzione a non cadere in tranelli. In questo caso $y$ è dichiarato nel $l\et$, ma se tale funzione anonima fosse stata valutata in esterno, $y$ è un valore "unbound", non è in ambiente.
Forse lo hai scritto apposta, ma attenzione

comunque in: fun x -> (x,y)
attenzione a non cadere in tranelli. In questo caso $y$ è dichiarato nel $l\et$, ma se tale funzione anonima fosse stata valutata in esterno, $y$ è un valore "unbound", non è in ambiente.
Forse lo hai scritto apposta, ma attenzione

@ham_burst: in effetti non conosco Ocaml ma questa cosa dei tipi funziona in ogni linguaggio funzionale nello stesso modo.. Ma se y non fosse stato definito nella funzione (o altrove) immagino ci sarebbe stato un errori di compilazione... O almeno così funziona in haskell (sto usando l'interprete):
Non riesco a inserirlo correttamente per via di qualche problema del forum con il simbolo di citazione sinistra che usa haskell..
Prelude> let x = (x, y) <interactive>:1:13: Not in scope: `y'
Non riesco a inserirlo correttamente per via di qualche problema del forum con il simbolo di citazione sinistra che usa haskell..
ah si certo, l'errore sarebbe saltato fuori immediatamente anche in O'Caml sia interprete (se l'ambiete non ha in memoria una instanza di $y$) che compile.
Lo scopo del mio messaggio, era al fatto, dato che ~Rose penso stia facendo un esame dove:
perciò senza interprete, ma con carta e penna....
se avesse una funzione che ha all'interno una variabile non legata (free), e scrivesse che ha tipo, invece di riportare un errore, sarebbe molto sbagliato.
Poi Haskell è un parente stretto di O'Caml come ogni linguaggio funzionale, perciò il comportamento di uno si può capire dal suo fratello (o sorella
)
Lo scopo del mio messaggio, era al fatto, dato che ~Rose penso stia facendo un esame dove:
in particolare dove bisogna determinare l'inferenza di tipo di alcune funzioni (ovviamente senza pc XD)
perciò senza interprete, ma con carta e penna....
se avesse una funzione che ha all'interno una variabile non legata (free), e scrivesse che ha tipo, invece di riportare un errore, sarebbe molto sbagliato.

Poi Haskell è un parente stretto di O'Caml come ogni linguaggio funzionale, perciò il comportamento di uno si può capire dal suo fratello (o sorella

[OT]Se ci si limita alle funzionalità classiche dei linguaggi funzionali si riesce ovviamente a capirsi. Più complicato è però quando si vanno a toccare concetti più unici. Una cosa che credo sia quasi unica ad haskell è l'enorme quantità di idee che prende dalla teoria delle categorie (i monadi sono il più evidente ma c'è anche dei moduli chiamati Control.Category e Control.Arrows che riprendono alcune strutture particolari). In generale credo che in haskell si sia cercato di trovare soluzioni più creative e diverse. Avendo in effetti deciso di rimanere "puri" è stato necessario trovare delle soluzioni funzionali per poter scrivere in modo iterativo. Ed è poi stato scelto da molti come un linguaggio di ricerca (forse proprio per la sua purezza) e sono uscite cose molto particolare e interessanti.[/OT]
Praticamente il primo esercizio del compito consiste tutto in funzioncine tipo quelle nel primo post, in cui bisogna determinare il tipo oppure il valore (di solito quando c'è un bool c'è un valore), oppure scrivere se è non tipizzabile... Poi c'è un altro esercizio dove ci sono frammenti di funzioni in cui bisogna determinare tipo e cosa fanno... Poi uno con frammenti da correggere qualche errore e determinare lo scopo della funzione ed infine un ultimo dove, a partire da una tipizzazione data, bisogna creare una funzione che faccia quello richiesto dal testo... Il tutto in un'ora, ovviamente scritto senza nessun ausilio di computer appunti eccetera o.o
Comunque, siccome ho poche nozioni di teoria, potreste spiegarmi meglio il concetto delle parentesi a sinistra? Credo che così capirei meglio, ma su internet si trova veramente poco su OCaml e soprattutto, su questa fantomatica inferenza di tipi (perfino sul sito ufficiale di OCaml c'è scritto " non entreremo nel dettaglio della spiegazione di questo meccanismo")... se non c'è li non saprei dove sbattere la testa... su wiki è riferito ad un altro linguaggio (se non ho capito male) e nelle slides del mio prof è spiegato solo per funzioni così semplici che lo vedo a occhio <.<
Comunque, siccome ho poche nozioni di teoria, potreste spiegarmi meglio il concetto delle parentesi a sinistra? Credo che così capirei meglio, ma su internet si trova veramente poco su OCaml e soprattutto, su questa fantomatica inferenza di tipi (perfino sul sito ufficiale di OCaml c'è scritto " non entreremo nel dettaglio della spiegazione di questo meccanismo")... se non c'è li non saprei dove sbattere la testa... su wiki è riferito ad un altro linguaggio (se non ho capito male) e nelle slides del mio prof è spiegato solo per funzioni così semplici che lo vedo a occhio <.<
Per prima cosa l'applicazione parziale di alcuni argomenti della funzione prende il nome di currying. In fatto di documentazione Haskell non è tanto male, puoi provare a vedere se ad esempio questa pagina ti può essere un po' di aiuto o forse anche al capitolo di uno dei tutorial/guide che preferisco. La sintassi è un po' diverso, ma credo che i concetti in quelle guide siano un po' universali a tutti i linguaggi e che alla fine fine quelle righe di codice dovrebbero essere abbastanza semplici da comprendere. Alcune cose importanti sono che 'a, 'b.. diventano semplicemente a, b, c.. in haskell. Tutti i tipi sono con lettere maiuscole, le funzioni iniziano con lettera minuscola, non ci sono terminatori per il codice (niente ;;) ma contano gli 'a capo'. map e le altre funzioni simili non richiedono il List. davanti anche se sarebbe possibile usare una sintassi simile. Va bhe.. non mi viene in mente nient'altro di fondamentale, se hai problemi cercherò di tradurre il codice.
potreste spiegarmi meglio il concetto delle parentesi a sinistra
lasciamo stare, pensavo che conoscessi l'ordine di valutazione che l'interprete di O'Caml utilizza, è solo un modo di vedere, ma utile solo se conosci l'interprete.
Una cosa mi è venuta in mente, che forse ti aiuta in questi "esercizi" di tipizzazione. Mai sentito parlare di "albero delle derivazioni" o "albero dei tipi" o anche "albero di inferenza"?
Un'altra piccola nota in merito all'utilizzo delle parentesi, non vorrei confonderti, ma c'è da stare attenti:
la funzione originale List.map: ('a->'b) -> 'a list -> 'b list
e si aggiunge le parentesi su carta: ('a->'b) -> ('a list -> 'b list)
si ha una seconda funzione $P$ e si da in pasto all'interprete: ('a->'b) -> ('a list -> 'b list)
Le due funzioni hanno significato molto diverso:
List.map ha due input (funzione + lista) e output (lista)
$P$ ha un input (funzione) e output (funzione)
questo per dire, va bene aggiungere su carta le parentesi per semplificarti la vita, ma attenzione a non confondere ciò che scrive l'interprete, le parentesi dell'interprete hanno significato diverso a seconda del contesto

[OT]
sì la parte comune è ML, poi si sono ramificati in modo diverso. Haskell è funzionale, con molte ottimizzazioni a compilatore (ottimizza molto le funzioni tail recursion), O'Caml ha molte aggiunte di linguaggi imperativi perciò è "sporco"

Con O'Caml si possono scrivere anche compilatori (per un progetto d'esame sto utilizzando créme CAraMeL), con Haskell non so se esite la modularizzazione e molte feature imperative...
[/OT]
Buono, mi darò una letta anche ai link che mi avete postato, speriamo in bene! Purtroppo è stato un corso non molto ricco di teoria, semplicemente di ogni funzione ci facevano vedere la tipizzazione ma senza dilungarsi troppo a lungo... Grazie ancora a tutti, riflettendoci meglio sono sicura che ci arriverò!
Ps. no niente alberi di tipi, mi spiace....
Ps. no niente alberi di tipi, mi spiace....

"ham_burst":
Un'altra piccola nota in merito all'utilizzo delle parentesi, non vorrei confonderti, ma c'è da stare attenti:
la funzione originale List.map: ('a->'b) -> 'a list -> 'b list
e si aggiunge le parentesi su carta: ('a->'b) -> ('a list -> 'b list)
si ha una seconda funzione $P$ e si da in pasto all'interprete: ('a->'b) -> ('a list -> 'b list)
Le due funzioni hanno significato molto diverso:
List.map ha due input (funzione + lista) e output (lista)
$P$ ha un input (funzione) e output (funzione)
questo per dire, va bene aggiungere su carta le parentesi per semplificarti la vita, ma attenzione a non confondere ciò che scrive l'interprete, le parentesi dell'interprete hanno significato diverso a seconda del contesto
[OT]
sì la parte comune è ML, poi si sono ramificati in modo diverso. Haskell è funzionale, con molte ottimizzazioni a compilatore (ottimizza molto le funzioni tail recursion), O'Caml ha molte aggiunte di linguaggi imperativi perciò è "sporco"
Con O'Caml si possono scrivere anche compilatori (per un progetto d'esame sto utilizzando créme CAraMeL), con Haskell non so se esite la modularizzazione e molte feature imperative...
[/OT]
È interessante che tu consideri diverse le due cose, perché in haskell non c'è affatto differenza.. te lo mostro con un esempio stupido. Ho salvato su un file test.hs il seguente codice:
m :: (Num t) => t -> (t -> t) m a = (a *)
La funzione ha come argomento un numero e restituisce la funzione moltiplicazione a sinistra per quel numero. Vediamo allora di usarla nell'interprete:
Prelude> :load "test.hs" [1 of 1] Compiling Main ( test.hs, interpreted ) Ok, modules loaded: Main. *Main> :t m m :: Num t => t -> t -> t *Main> m 10 57 570 it :: Integer *Main> map (m 5) [1..10] [5,10,15,20,25,30,35,40,45,50] it :: [Integer]
Vedi come il tipo ha di colpo perso le parentesi? Discorso diverso si ha invece in haskell se la parentesi compare in mezzo e non nel pezzo finale (di fatto è come se ci fossero sempre le parentesi sulla sinistra in haskell):
r :: (Num b) => (a -> b) -> a -> b -> b r f a = ((f a) * )
e output su interprete:
Prelude> :load "test.hs" [1 of 1] Compiling Main ( test.hs, interpreted ) Ok, modules loaded: Main. *Main> :t r r :: Num b => (a -> b) -> a -> b -> b *Main> r (fromIntegral) 7 2.5 17.5 it :: Double
Quindi in Ocaml è diverso?
Un'importantissima differenza tra haskell e ocaml che mi sono dimenticato è che haskell è lazy di default. Nel senso che a meno di usare istruzioni particolare per forzare la valutazione di un'espressione immediatamente, questa viene procrastinata finché non si richiede necessaria. Questo ha delle importanti conseguenze:
1. È possibile definire strutture dati, liste per esempio, infinite. Il seguente è un esempio in cui vengono calcolati tutti i numeri di fibonacci (è anche abbastanza efficiente):
Prelude> let fib = 1 : 1: zipWith (+) fib (tail fib) fib :: [Integer] (0.00 secs, 0 bytes) Prelude> take 20 fib [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765] it :: [Integer] (0.00 secs, 0 bytes) Prelude> fib !! 10000 544383731135652813387342609937503801353891845546959670262477158412085828656223490170830515479389605411738226 759780263173843595847511162414391747026429591699255863341179060630480897935314761084662590727593678991506779 600883065979666419658249377218003814411588410424809979846964873753371800281637633177819279411013692627509795 098007135967180238147106699126442147752544785876745689638080029622651331113599297627266794414001015758000435 107774659358053625024617079180592264146790056907523218958681423678495938807564234837543863426396359707337562 600989624626687461120417398194048750624437098686543156268471861956201461266422327118150403670188252053148458 758171935335298278378003519025292395178366894676619179538847124410284639354494846144507787625295209618875972 728892207685373964758695431591724345371936112637439263373130058961672480517379863063681150030883967495871026 195246313524474995052041983051871683216232838597946272459197714546282183996957892237989121994317754697052161 310810965599506382972612538482420078971090547540284381496119304650618661701229832889643527337507927860694447 618535251444210779280459799045612981294238091560550330323389196091622366987599227829231918966880177185755555 209946533201284465023711537151417492909131048972034555775071966454252328620220195060914835852238827110167084 330511699421157751512555102516559318881640483441295570388254775211115773957801158683970726025656148249564605 387002803313118614853998053970315557275296933995860798503815814462764338588285295358034248508454264464716815 310015331804795674363968156533261525095711274804119281960221488491482843891241785201745073055389287178579235 094177433833315068982393544219888054293324403711948672155435765485654991345192710989198026651845649278278272 129576492402355075955582056475693653948733176590002063731265706435097094826497100387335174777134033190281055 756679317894700241188030946040343629534719974613922747915497303564126330742308240519999961015497846673404583 268529603883011207656292459981362516523470939630497340464451063653041636308236692422577614682884617918432247 93434406079917883360676846711185597501 it :: Integer (0.03 secs, 7878916 bytes)
Nota che è interpretato, compilato è molto più veloce ed efficiente dal punto di vista della memoria.
2. Se un'espressione non serve non viene valutata:
f = let g = error "Se g viene valutato viene stampato un errore." in True || g
che quando eseguito restituisce:
*Main> f True
Soprattutto per questa funzionalità, il codice scritto in Ocaml è spesso più veloce di quello in haskell anche se migliora sempre di più e su alcuni problemi haskell eccelle. E' comunque molto difficile scrivere un codice molto efficiente in haskell (ma ci sono cose che aiutano). È comunque certamente possibile scrivere un compilatore in haskell (il principale compilatore per haskell è scritto in haskell) ed è anche facile definire facilmente domain specific languages (DSL) al suo interno (come in questo caso). Haskell è funzionale puro per cui è necessario usare trucchi per poter programmare in modo interativo. Questo trucco sono i monad e le più recenti arrows. Sono un po' complicati da spiegare, ma molto potenti come concetti. Esistono poi anche alcune funzionalità che ricordano funzionalità dei linguaggi ad oggetti come le type classes che ricordano le interfacce (le ho usate prime quando ho scritto Num t). Credo che nessun linguaggio moderno possa infine vivere senza una qualche modularizzazione. E haskell è abbastanza nuovo da essersene reso conto (è però abbastanza recente l'aggiunta di una libreria gerarchica). Credo di essermi dilungato fin troppo in questo OT.
@~Rose: C'è ancora qualcosa che non capisci? Io credo che il modo migliore per imparare a fare gli esercizi che ti sono richiesti sia avere a fianco l'interprete per vedere se hai compreso la soluzione. E scrivere diversi programmi giocattolo per prendere familiarità con il linguaggio e il suo funzionamento.
Per la teoria funzionale proprio in OCaml, ho utilizzato diverse volte il libro "Introduzione alla Programmazione Funzionale" di Limongelli & Mayer, si divide in due parte, la prima è tutta su O'Caml, tipizzazione, modularizzazione, e tutto quello che vuoi, a me era stato utile.
poi il consiglio di apatriarca è quello che feci io al tempo dell'esame, fare esercizi con l'interprete come aiuto...
@apatriarca: a interessante, se utilizza la lazy evaluation sì che utilizza il paradigma funzionale puro. Invece Caml e il derivato O'Caml è eager. Questa differenza me la ero dimentica... ma sta differenza, quando si usa eager non si nota, perchè se una valutazione in eager va in "loop" (facilmente) si utilizza lazy (in Caml almeno).
Sono andato a vedere il manuale, e ho notato che quello che dicevo delle parentesi è solo in parte vero, le parentesi nella valutazione vengono mantenute se e solo se c'è ambiguità.
es:
in questo caso non essendocene non sono mantenute, ricordavo male...sorry
perciò il comportamento dei tipi è identico ad Haskell...
@~Rose: Penso che questa cosa ti può tornare utile all'esame. Se ti chiedono di scrivere una funzione che restituisca come tipi: (int -> int) -> int -> int
puoi scrivere sia una versione come quella sopra, sia una così:
sono equivalente, anche se hanno un significato semantico differente
poi il consiglio di apatriarca è quello che feci io al tempo dell'esame, fare esercizi con l'interprete come aiuto...
@apatriarca: a interessante, se utilizza la lazy evaluation sì che utilizza il paradigma funzionale puro. Invece Caml e il derivato O'Caml è eager. Questa differenza me la ero dimentica... ma sta differenza, quando si usa eager non si nota, perchè se una valutazione in eager va in "loop" (facilmente) si utilizza lazy (in Caml almeno).
Sono andato a vedere il manuale, e ho notato che quello che dicevo delle parentesi è solo in parte vero, le parentesi nella valutazione vengono mantenute se e solo se c'è ambiguità.
es:
let f f1 = if ((f1 2)+2)=3 then f1 else f1;; val f: (int -> int) -> int -> int
in questo caso non essendocene non sono mantenute, ricordavo male...sorry
perciò il comportamento dei tipi è identico ad Haskell...
@~Rose: Penso che questa cosa ti può tornare utile all'esame. Se ti chiedono di scrivere una funzione che restituisca come tipi: (int -> int) -> int -> int
puoi scrivere sia una versione come quella sopra, sia una così:
let f f1 a= if ((f1 2)+2)=3 then a+2 else a;; val f: (int -> int) -> int -> int
sono equivalente, anche se hanno un significato semantico differente
