Shawn Zhang
Shawn Zhang

Reputation: 1884

How to implement zipWith N in Haskell

In my code, zipWith7 works well but now the function in zipWith7 require extra arity .

zipWith8 (\a b c d e f g h-> ....) List-A  ... List-H

while haskell base library only support up to 7 arity version.

Is there a way that can expand the function to N likr zipWithN ?

Upvotes: 0

Views: 307

Answers (3)

MaroonSphinx
MaroonSphinx

Reputation: 95

I have been trying to implement the same thing. This was the first solution I came up with, which seems to work.

zipWithN :: ([a] -> b) -> [[a]] -> [b]
zipWithN f = go where
  go [] = []
  go lists | any null lists = []
           | otherwise =
             let heads = map head lists
             in f heads : go (map tail lists)

In effect, it truncates the lengths of the lists to be equal in length, then transposes them, and applies f to them. zipWithN product [[1,2],[2,4],[3,6,3490]] is then [6, 48].

I thought there had to be a better way than to check the entire list for empty lists every single time, and something better than using null, head, and tail, which are partial functions. Based on this other stackoverflow post, I came up with the following:

zipWithN :: ([a] -> b) -> [[a]] -> [b]
zipWithN f = map f . getZipList . sequenceA . map ZipList

Or, written with more intermediate variable names:

zipWithN :: ([a] -> b) -> [[a]] -> [b]
zipWithN f xs =
  let zipLists = map ZipList xs
      truncatedTransposedZLs = sequenceA zipLists
  in map f (getZipList truncatedTransposedZLs)

Note that ZipList is in Control.Applicative.

Edit: This can be simplified to

zipWithN :: ([a] -> b) -> [[a]] -> [b]
zipWithN f = map f . getZipList . traverse ZipList

Upvotes: 1

Robin Zigmond
Robin Zigmond

Reputation: 18249

One simple way to do this (not that zipWith8 and friends are really commonly needed!) is to make use of the ZipList type and the Applicative operators.

zipWith8 f a b c d e f g h = getZipList $
  f <$> ZipList a
    <*> ZipList b
    <*> ZipList c
    <*> ZipList d
    <*> ZipList e
    <*> ZipList f
    <*> ZipList g
    <*> ZipList h

I'm sure you can figure out from this to write zipWith9 etc if you wanted to.

Aside: this is mentioned in the chapter of Learn You a Haskell dealing with Applicatives:

Aside from zipWith, the standard library has functions such as zipWith3, zipWith4, all the way up to 7. zipWith takes a function that takes two parameters and zips two lists with it. zipWith3 takes a function that takes three parameters and zips three lists with it, and so on. By using zip lists with an applicative style, we don't have to have a separate zip function for each number of lists that we want to zip together. We just use the applicative style to zip together an arbitrary amount of lists with a function, and that's pretty cool.

Upvotes: 3

chi
chi

Reputation: 116139

You can zip the first two lists to reduce the arity by one, if you also uncurry the function:

zipWithNplus1 f xs1 xs2 xs3 ... xsNplus1 =
   zipWithN (uncurry f) (zip xs1 xs2) xs3 ... xsNplus1

Otherwise, enable ParallelListComp and write a parallel list comprehension of any arity:

[ f x1 ... xN
| x1 <- xs1
| x2 <- xs2
| x3 <- xs3
...
| xN <- xsN
]

Upvotes: 3

Related Questions