Reputation: 7368
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
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
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
Reputation: 48662
<-
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