5

I'm having trouble understanding how this Haskell expression works:

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

What is the . flip hPutStrLn part doing exactly? The type signatures seem complicated:

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

What becomes the left and right operands of the (.) operator as the expression is evaluated?

Another way to putting my question is, how does the left part of the expression at the top end up with a type signature like this:

(forM_ [stdout, stderr] . flip hPutStrLn) :: String -> IO ()
Will Ness
  • 70,110
  • 9
  • 98
  • 181
dan
  • 43,914
  • 47
  • 153
  • 254
  • does comparing `:type (. hPutStrLn)` with `:type (. flip hPutStrLn)` help at all? – גלעד ברקן Feb 27 '13 at 15:43
  • 1
    Who would write such code? The point-free style is really pointless here and hurts readability, IMHO – Niklas B. Feb 27 '13 at 16:00
  • 1
    @NiklasB. it's not too bad to me, though I remember a time in the past when it would have been totally opaque. It depends on your familiarity with the style, I think (or rather, obviously). – luqui Feb 28 '13 at 01:00
  • this might have been more readable ``forM_ [stdout, stderr] (`hPutStrLn` "hello world")``, but YMMV. See also [three "laws" of operator sections](http://stackoverflow.com/questions/13139969/currying-3-arguments-in-haskell/13147064#13147064). – Will Ness Feb 28 '13 at 18:05

3 Answers3

13

The left and right operands of (.) are

forM_ [stdout, stderr]

and

flip hPutStrLn

respectively.

The type of hPutStrLn is

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

so flip hPutStrLn has type

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

As the type system tells you, flip is a combinator that swaps the order of another function’s arguments. Specified in the abstract

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

From ghci you already know that the type of (. flip hPutStrLn) is

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

Working from the other direction, the type of the left side is

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

Observe how the types fit together.

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

Combining the two (calling the first with the second) gives

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

In your question, the result of the composition is applied to a String, and that produces an I/O action that yields (), i.e., we are mainly interested in its side effects of writing to the standard output and error streams.

With point-free style such as the definition in your question, the programmer defines more complex functions in terms of smaller, simpler functions by composing them with (.). The flip combinator is useful for reordering arguments so as to make repeated partial applications fit together.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
7

flip reverses the arguments of an input function, i.e.:

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

The . is a function composition operator (or infix function), which lets you nicely chain functions together. Without this operator your expression can be rewritten as follows:

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

which is the same as:

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

or, using the application operator:

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

Concerning the . operands question. Consider the type signature of .:

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

You can view it as a function from 3 arguments: a function b -> c, a function a -> b and a value a - to a resulting value c, but also due to Currying, you can see it as a function from two arguments: b -> c and a -> b - to a result function of type a -> c. And this is what happens in your example: you pass two functions (forM_ [stdout, stderr] and flip hPutStrLn, which are themselves results of currying) to . and get a function of type String -> IO () as a result.

Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • 3
    Which is the same as `forM_ [stdout, stderr] (\h -> hPutStrLn h "hello world")` – luqui Feb 27 '13 at 15:42
  • What are the left and right operands of the (.) function in the above example? – dan Feb 27 '13 at 15:46
  • OK I think I get it, but this confused me because what is being composed is `String -> Handle -> IO ()` and `(Handle -> IO b) -> IO ()`. The trick seems to be seeing `Handle -> IO ()` as a unit. – dan Feb 27 '13 at 16:13
  • Plus I've never seen `forM_ [x, y]` by itself on the left side of a (.) – dan Feb 27 '13 at 16:15
2

Here's a somewhat shorter derivation of that type (as hinted in the 2nd part of Nikita Volkov's answer).

Knowing (.) :: (b -> c) -> (a -> b) -> a -> c and (f . g) x = f (g x), so that

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

(the b in a -> b and b -> c disappears after performing the unification, giving the a -> c type) and since

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

(we put parentheses around the Handle -> IO () in the first type, using the fact that in types -> is right associative), the resulting type of composing the second with the first (via the function composition operator) is

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

i.e. String -> IO ().

The order of arguments for (.) takes a little getting used to; it fires up its second argument function first, and then uses the result to call its first argument function. If we import Control.Arrow we can then use >>> operator which is like (.) in reverse, with functions: (f . g) x == (g >>> f) x.

Will Ness
  • 70,110
  • 9
  • 98
  • 181