Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Haskell, concat builds a list lazily but my own version with a twist, doesn't

I'm trying to make a relatively simple function that's almost concat but with a little twist. It's supposed to binary or together the last and first elements of each list and combine them in the process. I'm working to learn to write code that can take advantage of the Stream Fusion capabilities in Data.List.Stream

I checked that the concat in base does what is needed and builds the list lazily, however, this version I created doesn't. in the base, concat is specified as follows:

concat :: [[a]] -> [a]
concat = foldr (++) []

(++) :: [a] -> [a] -> [a]
(++) []     ys = ys
(++) (x:xs) ys = x : xs ++ ys

Here's my code:

bconcat :: [[Word8]] -> [Word8]
bconcat = foldr1 bappend

bappend :: [Word8] -> [Word8] -> [Word8]
bappend as bs = init as ++ (last as .|. head bs) : tail bs

The question I have is, how do I write this so the list is built lazily? I even tried writing bappend by mimicing the (++) definition in base but it didn't make a difference.

At the moment, I use the following code, it works the way I want but the performance falls behind concat. Also, it uses explicit recursion which I'd like to avoid.

bconcat :: [[Word8]] -> [Word8]
bconcat (a:b:xs) = init a ++ bconcat ((bappend (last a) b):xs)
bconcat (a:[]) = a
bconcat [] = []

bappend :: Word8 -> [Word8] -> [Word8]
bappend !a bs = (a .|. head bs) : tail bs

So, the question I have is, how do I write this code so it builds the list lazily and without explicit recursion?

Thank you for your time.

Edit:

My primary interest, for the moment, is making clean, concise and understable code with the standard combinators. I'm still very much a beginner with functional thinking and would love to see a reasonably efficient way of putting them to use here.

like image 821
Elriel Avatar asked Mar 19 '11 13:03

Elriel


2 Answers

Your definition looks strict to me. For instance, try evaluating

length $ bconcat [[1,2,3],[4,undefined,6]]

But you could be building up thunks for the .|. expression. Perhaps you want to force that.

You could always fuse things yourself if they don't fuse well automatically:

 bconcat [] = error "bconcat: empty outer list"
 bconcat (xs:xss) = loop xss xs
   where loop [] ys = ys
         loop ((x:xs):xss) [y] = let z = x .|. y in seq z $ z : loop xss xs
         loop ([]:_) _ = error "bconcat: empty inner list"
         loop xss (y:ys) = y : loop xss ys
like image 162
augustss Avatar answered Nov 15 '22 09:11

augustss


I can change the answer by augustuss to more closely match the original problem by writing:

bconcat2 [] = []
bconcat2 (xs:xss) = loop xss xs
  where loop [] ys = ys
        loop xss (y:[]) = gather xss y
        loop xss (y:ys) = y : loop xss ys
        loop (xs:xss) [] = loop xss xs
        gather [] z = [z]
        gather ([]:xss) z = gather xss z
        gather ((x:[]):xss) z = gather xss $! z .|. x
        gather ((x:xs):xss) z = let z' = z .|. x in z' : loop xss xs

The above ignores any empty internal lists.

like image 27
Chris Kuklewicz Avatar answered Nov 15 '22 10:11

Chris Kuklewicz