Search code examples
haskellrepa

Repa: ZipWith'ing over Columns


I am wondering if there is a faster/better way to the write the following repa function

zipOverColumns ::  (Source r1 a, Source r2 b) 
               => (a -> b -> c) 
               -> Array r1 DIM1 a 
               -> Array r2 DIM2 b 
               -> Array D DIM2 c
zipOverColumns f x y = traverse y id lookup where
    lookup get sh@(Z :. _ :. r) = f (x ! (Z :. r)) $ get sh

Here is some sample output

>>> toList $ zipOverColumns (*) (fromListUnboxed (Z :. 3)      [1,2,3]) 
                                (fromListUnboxed (Z :. 2 :. 3) [4,5,6,7,8,9])
[4.0,10.0,18.0,7.0,16.0,27.0]

Solution

  • In Repa 3 context, your implementation is performance-optimal. You could find the following version a bit more readable:

    zipOverColumns2
        :: (Source r1 a, Source r2 b)
        => (a -> b -> c)
        -> Array r1 DIM1 a
        -> Array r2 DIM2 b
        -> Array D DIM2 c
    zipOverColumns2 f col arr = zipWith f ccol arr
      where
        ccol = fromFunction (extent arr) (\(Z :. _ :. r) -> col ! (Z :. r))
    

    zipOverColumns2 is also 15% faster than the original, but it's just a coincidence.

    With yarr the function looks like:

    zipOverColumns
        :: (USource r1 l1 Dim1 a,
            USource r2 l2 Dim2 b, DefaultIFusion r2 l2 fr fl Dim2,
            USource fr fl Dim2 c)
        => (a -> b -> c)
        -> UArray r1 l1 Dim1 a
        -> UArray r2 l2 Dim2 b
        -> UArray fr fl Dim2 c
    zipOverColumns f col arr = imapM mapF arr
      where
        mapF (r, _) b = do
            a <- col `index` r
            return $ f a b
    

    This version is from 30 to 85 % faster, depending on column size (I have tested 4, 8 and 1000). Gist for playing with it.