Using map with two lists rather than one. Can you

2019-04-07 03:05发布

I need to run a function that takes two arguments several times. I have two lists containing these arguments and I'd like to be able to use map or something similar to call the function with the corresponding args.

The function I want to call has this type:

runParseTest :: String -> String -> IO()

The lists are created like this:

-- Get list of files in libraries directory
files <- getDirectoryContents "tests/libraries"
-- Filter out ".." and "." and add path
let names = filter (\x -> head x /= '.') files
let libs = ["tests/libraries/" ++ f | f <- names]

So lets say that names contains ["test1.js", "test2.js", "test3.js"] and libs contains ["tests/libraries/test1.js", "tests/libraries/test2.js", "tests/libraries/test3.js"]

I want to call them like this:

runParseTest "test1.js" "tests/libraries/test1.js"
runParseTest "test2.js" "tests/libraries/test2.js"
runParseTest "test3.js" "tests/libraries/test3.js"

I know I could create a helper function that does this fairly easily, but out of interest, is it possible to do in one line using map?

This is what I have so far, but obviously the first argument is always "test":

mapM_ (runParseTest "test") libs

I apologise if this is unclear. I can provide more info if necessary.

4条回答
叛逆
2楼-- · 2019-04-07 03:36

While waiting for answers I created a solution of my own with a new function called map2M_ based on the source code for map and mapM_:

map2 :: (a -> b -> c) -> [a] -> [b] -> [c]
map2 _ [] _          = []
map2 _ _ []          = []
map2 f (a:as) (b:bs) = f a b : map2 f as bs

map2M_ :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m ()
map2M_ f as bs =  sequence_ (map2 f as bs)
查看更多
神经病院院长
3楼-- · 2019-04-07 03:44

So lets say that names contains ["test1.js", "test2.js", "test3.js"] and libs contains ["tests/libraries/test1.js", "tests/libraries/test2.js", "tests/libraries/test3.js"]

I want to call them like this:

runParseTest "test1.js" "tests/libraries/test1.js" runParseTest "test2.js" "tests/libraries/test2.js" runParseTest "test3.js" "tests/libraries/test3.js"

It's possible to do that with zip:

map (\(a,b) -> runParseTest a b) $ zip names libs

Or maybe uncurry runParseTest:

 map (uncurry runParseTest) $ zip names libs

Or with zipWith:

 zipWith runParseTest names libs

And like Ozgur said, there are some analogues for monads:

> :t zipWithM
zipWithM :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m [c]
> :t zipWithM_
zipWithM_ :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m ()
查看更多
相关推荐>>
4楼-- · 2019-04-07 03:50

You are looking for zipWithM_.

You say you could write a helper function which does this. Which means you know the type of the function you are looking for. In such cases you can use hoogle.

(Try: Monad m => [a] -> [b] -> m ())

查看更多
地球回转人心会变
5楼-- · 2019-04-07 03:54

This is a great time to use Hoogle! Hoogle is a search engine for searching Haskell types. For instance, a Hoogle query for (a -> b) -> [a] -> [b] pulls up map. Here, you have a function of type String -> String -> IO (); you want a function of type (String -> String -> IO ()) -> [String] -> [String] -> IO (). Hoogle can often generalize by itself, but it's having trouble here, so let's help it out: You just want (a -> a -> IO ()) -> [a] -> [a] -> IO () for any a. If you Hoogle for that type signature, the first result is zipWithM_ :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m () in the Control.Monad module, which does exactly what you want. This is part of a family of functions, with varying degrees of generality:

So, in your specific use case, we're going to have—with a few extra changes—the following:

import Data.List (isPrefixOf)

...

-- I got rid of `head` because it's a partial function, and I prefer `map` to
-- list comprehensions for simple things    
do files <- getDirectoryContents "tests/libraries"
   let names = filter (not . ("." `isPrefixOf`)) files
       libs  = map ("tests/libraries/" ++) names
   zipWithM_ runParseTest names libs
查看更多
登录 后发表回答