2013-02-27 10 views
5

Sto avendo difficoltà a capire come funziona questa espressione Haskell:Si prega di spiegare (forM_ [stdout, stderr] capovolgere hPutStrLn.) :: String -> IO()

import Control.Monad 
import System.IO 
(forM_ [stdout, stderr] . flip hPutStrLn) "hello world" 

Qual è la parte . flip hPutStrLn fare esattamente ? Le firme di tipo sembrare complicato:

ghci> :type flip 
flip :: (a -> b -> c) -> b -> a -> c 
ghci> :type (.) 
(.) :: (b -> c) -> (a -> b) -> a -> c 
ghci> :type (. flip) 
(. flip) :: ((b -> a -> c1) -> c) -> (a -> b -> c1) -> c 
ghci> :type (. flip hPutStrLn) 
(. flip hPutStrLn) :: ((Handle -> IO()) -> c) -> String -> c 

ciò che diventa operandi sinistro e destro del (.) operatore l'espressione viene valutata?

Un altro modo per mettere la mia domanda è, come fa la parte sinistra dell'espressione all'estremità superiore con una firma di tipo simile:

(forM_ [stdout, stderr] . flip hPutStrLn) :: String -> IO() 
+0

confronta ': type (. HPutStrLn)' con ': type (. Flip hPutStrLn)' help a tutti? –

+1

Chi scriverebbe tale codice? Lo stile point-free è davvero inutile qui e fa male la leggibilità, IMHO –

+1

@NiklasB. non è male per me, anche se ricordo un periodo nel passato in cui sarebbe stato del tutto opaco. Dipende dalla tua familiarità con lo stile, penso (o meglio, ovviamente). – luqui

risposta

13

Gli operandi sinistro e destro del (.) sono

forM_ [stdout, stderr] 

e

flip hPutStrLn 

rispettivamente.

Il tipo di hPutStrLn è

hPutStrLn :: Handle -> String -> IO() 

così flip hPutStrLn è di tipo

flip hPutStrLn :: String -> Handle -> IO() 

Poiché il sistema tipo ti dice, flip è un combinatore che scambia l'ordine degli argomenti di un'altra funzione. Specificato in astratto

flip  :: (a -> b -> c) -> b -> a -> c 
flip f x y = f y x 

Da ghci voi già sanno che il tipo di (. flip hPutStrLn) è

ghci> :type (. flip hPutStrLn) 
(. flip hPutStrLn) :: ((Handle -> IO()) -> c) -> String -> c 

Lavorare dalla direzione opposta, il tipo del lato sinistro è

ghci> :type forM_ [stdout, stderr] 
forM_ [stdout, stderr] :: Monad m => (Handle -> m b) -> m() 

Osservare come si combinano i tipi.

(. flip hPutStrLn)  ::   ((Handle -> IO()) -> c ) -> String -> c 
forM_ [stdout, stderr] :: Monad m => (Handle -> m b) -> m() 

Combinando i due (chiamando il primo con il secondo) dà

ghci> :type forM_ [stdout, stderr] . flip hPutStrLn 
forM_ [stdout, stderr] . flip hPutStrLn :: String -> IO() 

nella sua interrogazione, il risultato della composizione viene applicata ad un String, e che produce un'azione I/O che cede (), ie, ci interessano principalmente i suoi effetti collaterali della scrittura sullo standard output e sui flussi di errore.

Con point-free style come la definizione nella domanda, il programmatore definisce funzioni più complesse in termini di funzioni più piccole e più semplici componendole con (.). Il combinatore flip è utile per riordinare gli argomenti in modo da far combaciare le applicazioni parziali ripetute.

+0

Risposta molto lucida. Grazie. – dan

+1

@dan Prego! Insisti. Il viaggio per imparare Haskell è meraviglioso. –

7

flip inverte gli argomenti di una funzione di ingresso, vale a dire:

flip hPutStrLn == \a b -> hPutStrLn b a 

Il . è un operatore di composizione di funzione (o la funzione infisso), che consente di ben funzioni a catena insieme. Senza questo operatore l'espressione può essere riscritta come segue:

forM_ [stdout, stderr] ((flip hPutStrLn) "hello world") 

che è la stessa come:

forM_ [stdout, stderr] (flip hPutStrLn "hello world") 

o, utilizzando l'operatore applicazione:

forM_ [stdout, stderr] $ flip hPutStrLn "hello world" 

riguardante l' . domanda di operandi. Si consideri la firma tipo di .:

(.) :: (b -> c) -> (a -> b) -> a -> c 

Puoi vederlo come una funzione da 3 argomenti: una funzione b -> c, una funzione a -> b ed un valore a - ad un valore risultante c, ma anche a causa di Currying, è può vederlo come una funzione da due argomenti: b -> c e a -> b - a una funzione risultato di tipo a -> c. E questo è ciò che accade nel tuo esempio: tu passi due funzioni (forM_ [stdout, stderr] e flip hPutStrLn, che sono esse stesse i risultati della vendita) a . e ottieni di conseguenza una funzione di tipo String -> IO().

+3

Che è uguale a 'forM_ [stdout, stderr] (\ h -> hPutStrLn h" hello world ")' – luqui

+0

Quali sono gli operandi di sinistra e destra della funzione (.) Nell'esempio sopra? – dan

+0

@dan Vedere l'aggiornamento –

2

Ecco una derivazione un po 'più breve di quel tipo (come suggerito nella seconda parte della risposta di Nikita Volkov).

Conoscere (.) :: (b -> c) -> (a -> b) -> a -> c e (f . g) x = f (g x), in modo che

(f . g) :: a -> c  where g :: (a -> b) and f :: (b -> c) 

(il b in a -> b e b -> c scompare dopo aver eseguito the unification, dando il tipo a -> c) e dal momento

flip hPutStrLn   :: String -> (Handle -> IO())    -- g 
forM_ [stdout, stderr] :: (Monad m) => (Handle -> m b) -> m()  -- f 

(mettiamo tra parentesi il Handle -> IO() nel primo tipo, usando il fatto che nei tipi -> è associativa a destra), il tipo risultante di comporre il secondo con il primo (tramite l'operatore function composition) è

(Monad m) => String -> m()  where m ~ IO and b ~() 
           (found by unification of 
             Handle -> IO() and 
             Handle -> m b  ) 

cioè String -> IO().

L'ordine degli argomenti per (.) si sta un po 'abituando; attiva prima la sua funzione secondo argomento, quindi usa il risultato per chiamare la sua funzione primo argomento.Se importiamo Control.Arrow possiamo quindi utilizzare l'operatore >>> che è come (.) al contrario, con funzioni: (f . g) x == (g >>> f) x.

Problemi correlati