A better way to map a function that requires IO over a list

前端 未结 4 1469
感动是毒
感动是毒 2021-01-20 03:40

So lately I have a list of strings, and need to independently go over each one and perform some IO function.

So basically what I have is this:

相关标签:
4条回答
  • 2021-01-20 04:14

    sepp2k and solrize are right to recommend mapM_. But, in the spirit of teaching you to fish instead of giving you a fish, here's something you can try in the future:

    1. Try to come up with the type signature of the operation that you need. For example, in your case, you want something of type (String -> IO ()) -> [String] -> IO ().
    2. Go to the Hoogle search engine for Haskell libraries and search for that type.
    3. That one has no results, so try modifying the type a bit to make it more generic. Replace String with a to get (a -> IO ()) -> [a] -> IO (), and search for that.

    Now the third result of the second search is mapM_ :: Monad m => (a -> m b) -> [a] -> m (), which is exactly what you want. (The first answer, closeFdWith :: (Fd -> IO ()) -> Fd -> IO () is not a relevant result; the second, traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f () is relevant but a bit more complicated to understand and use.)

    0 讨论(0)
  • 2021-01-20 04:16

    First, easy improvement:

    goOverList' :: [String] -> IO ()
    goOverList' []     = return ()
    goOverList' (x:xs) = do
        putStrLn x
        goOverList' xs
    

    The base case for recursion should be the empty list: in that case, you simply return the IO action return () which does nothing. When you have one or more elements, you print it and continue with the rest of the list, simple as that.

    The same exact thing is achievable in a way more compact definition with mapM_ :: Monad m => (a -> m b) -> [a] -> m (): it is the same as the regular map, except that it works with monadic actions. Instead of returning the collection of results m [b] like the regular mapM would do, it throws it away. It works just fine for you in this case, as you're are only interested in printing out the elements of he list.

    goOverList'' :: [String] -> IO ()
    goOverList'' = mapM_ putStrLn
    

    In order to be even more general, we could rely on print :: Show a => a -> IO () instead of putStrLn and accept in input every list of "showable" items:

    goOverList''' :: (Show a) => [a] -> IO ()
    goOverList''' = mapM_ print
    
    data T = One | Two | Three deriving (Show)
    
    main = do
        let myList = [One, Two, Three]
        goOverList''' myList
    
    0 讨论(0)
  • 2021-01-20 04:21

    Your goOverList function is almost equivalent to mapM_ putStrLn. (Just almost because mapM_ also works with the empty list while your function does not).

    mapM is a function that applies a function of type a -> IO b¹ to each item in a list of as and gives you back an IO² with a list of bs. mapM_ is the same as mapM except that it doesn't store the results in a list (which doesn't make sense for actions that return () like putStrLn does).

    ¹ Actually it's more general than that: The function has type a -> m b where Monad m, but in this case m is IO.

    ² Again it's actually m.

    0 讨论(0)
  • 2021-01-20 04:27

    Your goOverList function can be written mapM_ putStrLn where mapM_ is a function in the standard Prelude.

    You can also simplify your own implementation:

    goOverList :: [String] -> IO ()
    
    goOverList [] = return ()
    
    goOverList (x:xs) = do
        putStrLn x
        goOverList xs
    
    0 讨论(0)
提交回复
热议问题