2013-02-27 14 views
5

Ich habe Probleme zu verstehen, wie diese Haskell Ausdruck funktioniert:Bitte erläutern (Form_ [stdout, stderr] Flip hPutStrLn.) :: String -> IO()

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

Was das genau tun . flip hPutStrLn Teil ist ? Die Art Signaturen kompliziert erscheinen:

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 

Was sind die linken und rechten Operanden des (.) Operator als Ausdruck wird ausgewertet?

Eine andere Möglichkeit, meine Frage zu stellen ist, wie funktioniert der linke Teil des Ausdrucks am oberen Ende oben mit einer Art Signatur wie folgt aus:

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

vergleicht ': Typ (. HPutStrLn)' mit ': Typ (. Flip hPutStrLn)' Hilfe überhaupt? –

+1

Wer würde solchen Code schreiben? Der Punkt-freie Stil ist hier wirklich sinnlos und schmerzt Lesbarkeit, IMHO –

+1

@ NiklasB. Es ist nicht schlecht für mich, obwohl ich mich an eine Zeit in der Vergangenheit erinnere, als es völlig undurchsichtig gewesen wäre. Es hängt von deiner Vertrautheit mit dem Stil ab, denke ich (oder vielmehr, offensichtlich). – luqui

Antwort

13

Die linken und rechten Operanden von (.) sind

forM_ [stdout, stderr] 

und

flip hPutStrLn 

sind.

Die Art der hPutStrLn ist

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

so hat flip hPutStrLn

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

Wie das Typsystem geben Sie sagt Ihnen, flip ist ein Kombinator, der die Reihenfolge von einer anderen Funktion Argumente austauscht. in der Zusammenfassung angegeben

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

Von ghci Sie bereits wissen, dass die Art der (. flip hPutStrLn)

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

aus der anderen Richtung zu arbeiten ist, ist die Art von der linken Seite

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

beachten wie die Typen zusammenpassen.

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

Die Kombination der beiden (die erste mit dem zweiten Aufruf) gibt

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

In Ihrer Frage, das Ergebnis der Zusammensetzung auf eine String angelegt wird, und das erzeugt eine I/O-Aktion, Erträge (), dh, sind wir hauptsächlich an seinen Nebeneffekten des Schreibens auf die Standardausgabe und die Fehlerströme interessiert.

Mit point-free style wie die Definition in Ihrer Frage, der Programmierer definiert komplexere Funktionen in Bezug auf kleinere, einfachere Funktionen, indem sie mit (.) komponieren. Der flip Kombinator ist nützlich zum Umordnen von Argumenten, um wiederholte Teilanwendungen zusammenpassen zu lassen.

+0

Sehr klare Antwort. Vielen Dank. – dan

+1

@dan Gern geschehen! Bleib dabei. Die Reise, Haskell zu lernen, ist wunderbar. –

7

flip die Argumente einer Eingabefunktion umkehrt, das heißt:

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

Die . ist eine Funktion Zusammensetzung Operator (oder Infix-Funktion), die zusammen hübsch Kettenfunktionen können. Ohne diesen Operator kann Ihr Ausdruck wie folgt geschrieben werden:

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

, die die gleiche ist wie:

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

oder mit Hilfe der Anwendung Betreiber:

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

In Bezug auf die . Operanden Frage. Betrachten Sie die Art Signatur von .:

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

Sie können es als eine Funktion von drei Argumente an: Funktion b -> c, eine Funktion a -> b und ein Wert a - zu einem resultierenden Wert c, sondern auch wegen Currying, Sie kann es als eine Funktion aus zwei Argumenten sehen: b -> c und a -> b - zu einer Ergebnisfunktion des Typs a -> c. Und genau so verhält es sich in Ihrem Beispiel: Sie übergeben zwei Funktionen (forM_ [stdout, stderr] und flip hPutStrLn, die selbst Curry-Effekte sind) an . und erhalten dadurch eine Funktion vom Typ String -> IO().

+3

Was ist das gleiche wie 'forM_ [stdout, stderr] (\ h -> hPutStrLn h" Hallo Welt ")' – luqui

+0

Was sind die linken und rechten Operanden der Funktion (.) Im obigen Beispiel? – dan

+0

@dan Das Update sehen –

2

Hier ist eine etwas kürzere Ableitung dieses Typs (wie im zweiten Teil von Nikita Volkovs Antwort angedeutet).

(.) :: (b -> c) -> (a -> b) -> a -> c und (f . g) x = f (g x) Wissen, so dass

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

(die b in a -> b und b -> c verschwindet nach the unification durchgeführt wird, geben die a -> c Typ) und seit

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

(wir setzen in Klammern die Handle -> IO() in der ersten Art, mit der Tatsache, dass in den Typen -> Ist rechts assoziativen) die sich ergebende Art von der zweite mit dem ersten Zusammensetzen (über den function composition Operator)

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

d.h. String -> IO().

Die Reihenfolge der Argumente für (.) ist etwas gewöhnungsbedürftig; es startet zuerst seine zweite Argumentfunktion und verwendet dann das Ergebnis, um seine erste Argumentfunktion aufzurufen.Wenn wir Control.Arrow importieren, können wir dann >>> Operator verwenden, der wie (.) rückwärts ist, mit Funktionen: (f . g) x == (g >>> f) x.

Verwandte Themen