2016-05-27 10 views
14

Dato:comprensione Tipo di IO() in `let` Espressione

λ: let f = putStrLn "foo" in 42 
42 

Qual è il tipo f s'? Perché lo "foo" non viene stampato prima di mostrare il risultato di 42?

Infine, perché il seguente lavoro non funziona?

λ: :t f 

<interactive>:1:1: Not in scope: ‘f’ 
+0

Si potrebbe voler leggere su ['let' vs' <-'] (http://stackoverflow.com/q/28624408/3234959). Non è esattamente il problema che stai avendo ora, ma potrebbe essere d'aiuto. – chi

+0

Leggere [Perché non riesco a forzare un'azione IO con seq?] (Http://stackoverflow.com/q/20324446/510937) per ulteriori informazioni sulla valutazione e l'esecuzione di azioni IO. – Bakuriu

risposta

12

Qual è il tipo di 's f?

Come avete identificato correttamente, è IO() che può essere pensato come un'azione IO che restituisce nulla di utile (())

Perché "foo" non vengono stampati prima di mostrare il risultato di 42?

Haskell viene valutato pigramente, ma in questo caso nemmeno seq non è sufficiente. Un'azione IO verrà eseguita solo nel REPL se l'espressione restituisce l'azione IO. Un'azione IO verrà eseguita solo in un programma se viene restituito da main. Tuttavia, ci sono modi per aggirare questa limitazione.

Infine, perché il seguente lavoro non funziona?

Haskell let nomi un valore nell'ambito di un'espressione, così dopo l'espressione è stata valutata f va fuori portata.

7

f sarà di tipo IO().

"foo" non viene stampato perché f non è "vincolato" al mondo reale. (Non posso dire che questa sia una spiegazione amichevole. Se questo suona senza senso, potresti voler riferire qualche tutorial per cogliere l'idea di Monad e la valutazione pigra).

let name = value in (scope) rende il valore disponibile, ma non fuori dall'ambito, pertanto :t non lo troverà nell'oscilloscopio di livello superiore di ghci.

let senza in mette a disposizione :t (questo codice è valido solo in ghci):

> let f = putStrLn "foo" 
> :t f 
f :: IO() 
9

let f = ... definisce semplicemente f, e non significa "correre" nulla. È vagamente simile alla definizione di una nuova funzione nella programmazione imperativa.

Il codice completo let f = putStrLn "foo" in 42 potrebbe essere liberamente tradotto da

{ 
    function f() { 
    print("foo"); 
    } 
    return 42; 
} 

Non ci si aspetterebbe quanto sopra per stampare qualsiasi cosa, giusto?

A titolo di confronto, let f = putStrLn "foo" in do f; f; return 42 è simile a

{ 
    function f() { 
    print("foo"); 
    } 
    f(); 
    f(); 
    return 42; 
} 

La corrispondenza non è perfetto, ma si spera che si ottiene l'idea.

4

Ci sono due cose da fare qui.

primo luogo, considerare

let x = sum [1..1000000] in 42 

Haskell è pigro. Poiché in realtà non facciamo nulla con x, non viene mai calcolato. (Il che è altrettanto buono, perché sarebbe leggermente lento.) In effetti, se lo compili, il compilatore vedrà che x non viene mai usato e lo elimina (cioè non genera alcun codice compilato per esso).

Secondo, chiamare putStrLn in realtà non stampa nulla. Piuttosto, restituisce IO(), che può essere considerato come una sorta di "oggetto comando I/O". Il solo oggetto comando è diverso da che esegue. In base alla progettazione, l'unico modo per "eseguire" un oggetto comando I/O è restituirlo da main. Almeno, è in un programma completo; GHCi ha la caratteristica utile che se si inserisce un'espressione che restituisce un oggetto comando I/O, GHCi lo eseguirà per voi.

L'espressione restituisce 42; di nuovo, f non viene utilizzato, quindi non fa nulla.

Come sottolinea chi, è un po 'come dichiarare una funzione locale (zero-argument) ma mai chiamarla. Non ti aspetteresti di vedere alcun risultato.

Si può anche fare qualcosa di simile

actions = [print 5, print 6, print 7, print 8] 

Questo crea un elenco di oggetti di comando I/O. Ma, ancora una volta, non lo fa execute nessuno di loro.

In genere quando si scrive una funzione che esegue I/O, si tratta di un blocco di operazioni che raggruppa tutto in un unico oggetto comando I/O gigante e lo restituisce al chiamante. In tal caso, non è davvero necessario capire o fare qualcosa riguardo a questa distinzione tra che definisce un oggetto comando e in esecuzione su. Ma la distinzione è ancora lì.

È forse più facile vederlo con una monade che ha una funzione di esecuzione esplicita. Ad esempio, runST prende un oggetto comando ST, lo esegue e restituisce la risposta. Ma (diciamo) newSTVar, da solo, non fa altro che costruire un comando ST; devi runST che prima di tutto effettivamente "accade".

Problemi correlati