Ed I
Ed I

Reputation: 7368

Convert listDirectory map from Haskell do notation

Consider this Haskell script that appends an exclamation mark to every path in a directory:

import System.Directory

mapPaths paths = map (++ "!") paths

transformAll = do
    paths <- listDirectory "/"
    print $ mapPaths paths

main = transformAll

How can I convert this to a chain of bind operations from left to right while still using the function mapPaths as is?

I would like to understand how to compose a function that would look kind of like:

doSomething :: [String] => IO [String]
doSomething paths = ???

transformAll = listDirectory "/" >>= doSomething >>= print

Where doSomething has this signature and makes use of mapPaths.

Upvotes: 1

Views: 172

Answers (3)

chepner
chepner

Reputation: 532063

Like all monads, IO is also a functor, which means you can write

import System.Directory

mapPaths :: [FilePath] -> [FilePath]
mapPaths paths = map (++ "!") paths

-- Conceptually, printing is not part of the transformation of the paths;
-- leave it out of the definition.
transformAll :: IO [FilePath]
transformAll =  mapPaths <$> listDirectory "/"

main = transformAll >>= print

Upvotes: 2

amalloy
amalloy

Reputation: 92117

Joseph Sible has already shown how to desugar the do-notation into lambdas and proposed one solution using more powerful combinators, so I won't rehash that ground. I do want to point out as an aside, though, that (=<<), a "flipped" version of (>>=), often makes code easier to read, by letting you compose functions in the same textual order as you would in non-monadic code.

Imagine, for example, that instead of

 listDirectory "/" :: IO [String]

you had some other function that just produced a non-IO list of strings:

compute "/" :: [String]

To mapPaths and then print this, you might write

print . mapPaths $ compute "/"

Using (=<<) instead of (>>=) lets you keep this basic structure, just changing which operators are used, emphasizing the compositional nature more than the monadic operations:

print . mapPaths =<< listDirectory "/"

Upvotes: 2

<- desugars into >>= and a lambda, like this:

transformAll = listDirectory "/" >>= \paths -> print $ mapPaths paths

You can then write the function you want like this:

doSomething :: [String] -> IO [String]
doSomething paths = return $ mapPaths paths

By the way, since [String] is an argument type and not a constraint, you use a little arrow after it, not a big arrow. Also, IMO, having doSomething at all just makes things less clear for no reason (since it doesn't need to do any IO operations but now has IO in its type anyway). You could write transformAll = listDirectory "/" >>= print . mapPaths or transformAll = listDirectory "/" >>= (mapPaths <&> print) instead to avoid it (note the latter needs <&> from Data.Functor).

Upvotes: 2

Related Questions