combine-function with list-comprehension - haskell

I've a simple question:
combine :: (a -> b -> c) -> [a] -> [b] -> [c]
combine f (a:as) (b:bs) = f a b : combine f as bs
combine _ _ _ = [ ]
This is recursive. Now i want to use a list comprehension to solve the same problem:
combine f (x:xs) (y:ys) = [ f x y | x <- (x:xs), y <- (y:ys) ]
But my problem is the combination of elements. I only want to combine x1 y1, x2 y2, xs ys ... not x1 y1, x1 y2, x1 ys, x2 y1, x2 y2, ......
Thank you!

What you need is a parallel list compehension. To be able to use it you need to specify a ParallelListComp pragma to the compiler:
{-# LANGUAGE ParallelListComp #-}
combine :: (a -> b -> c) -> [a] -> [b] -> [c]
combine f xs ys = [ f x y | x <- xs | y <- ys ]
Compiler desugars it to an application of zipWith:
combine :: (a -> b -> c) -> [a] -> [b] -> [c]
combine f xs ys = zipWith f xs ys
Which is actually what your function is, so:
combine :: (a -> b -> c) -> [a] -> [b] -> [c]
combine = zipWith

Since list comprehension usually gives Cartesian product, you can also try ZipList from Control.Applicative
GHCi> :m + Control.Applicative
GHCi> :info ZipList
newtype ZipList a = ZipList {getZipList :: [a]}
-- Defined in `Control.Applicative'
instance Functor ZipList -- Defined in `Control.Applicative'
instance Applicative ZipList -- Defined in `Control.Applicative'
GHCi> let combine f xs ys = getZipList $ f <$> ZipList xs <*> ZipList ys
GHCi> :t combine
combine :: (a2 -> a1 -> a) -> [a2] -> [a1] -> [a]
GHCi> combine (-) [10,9..] [1..10]
[9,7,5,3,1,-1,-3,-5,-7,-9]
GHCi>
Don't fear of ZipList, it just wraps a list, you can convert a list into ZipList and convert it back using getZipList.
This chapter in LYAH gives you some explanation about how to use it, have fun!
BTW, you can use a bare list in the example above, which gives you the Cartesian product:
GHCi> let combine1 f xs ys = f <$> xs <*> ys
GHCi> :t combine1
combine1 :: Applicative f => (a1 -> a -> b) -> f a1 -> f a -> f b
GHCi> combine (-) [10,9..1] [1..10]
[9,8,7,6,5,4,3,2,1,0,8,7,6,5,4,3,2,1,0,-1,...]
GHCi>
As you can see, there are two ways of merging two lists together, one is to think elements in a list as possible results, if you take one possible value from xs=[1,2], and another possible value from ys=[3,4] to make a tuple, you will end up with all possibilities: [(1,3),(1,4),(2,3),(2,4)], this is basically what [(x,y)| x <- xs, y <-ys] is saying. But there is also another way of merging two lists: every time you take one element simultaneously from two lists, and make a pair, until one of the list reaches its end, this is how ZipList get merged together.

this would be my poor answer
kk :: ( a -> b -> c) -> (a,b) -> c
kk f (x,y) = f x y
combineFunk :: (a -> b -> c) -> [a] -> [b] -> [c]
combineFunk f xs ys = [cs | (a,b) <- zip xs ys , let cs = kk f (a,b) ]

Related

apply a function n times to the n-th item in a list in haskell

I want a higher-order function, g, that will apply another function, f, to a list of integers such that
g = [f x1, f(f x2), f(f(f x3)), … , f^n(xn)]
I know I can map a function like
g :: (Int -> Int) -> [Int] -> [Int]
g f xs = map f xs
and I could also apply a function n-times like
g f xs = [iterate f x !! n | x <- xs]
where n the number of times to apply the function. I know I need to use recursion, so I don't think either of these options will be useful.
Expected output:
g (+1) [1,2,3,4,5] = [2,4,6,8,10]
You can work with explicit recursion where you pass each time the function to apply and the tail of the list, so:
g :: (Int -> Int) -> [Int] -> [Int]
g f = go f
where go _ [] = []
go fi (x:xs) = … : go (f . fi) xs
I here leave implementing the … part as an exercise.
Another option is to work with two lists, a list of functions and a list of values. In that case the list of functions is iterate (f .) f: an infinite list of functions that can be applied. Then we can implement g as:
g :: (Int -> Int) -> [Int] -> [Int]
g f = zipWith ($) (iterate (f .) f)
Sounds like another use for foldr:
applyAsDeep :: (a -> a) -> [a] -> [a]
applyAsDeep f = foldr (\x xs -> f x : map f xs) []
λ> applyAsDeep (+10) [1,2,3,4,5]
[11,22,33,44,55]
If you want to go a bit overkill ...
import GHC.Exts (build)
g :: (a -> a) -> [a] -> [a]
g f xs0 =
build $ \c n ->
let go x r fi = fi x `c` r (f . fi)
in foldr go (const n) xs0 f

Haskell - Check if position is even/odd

What I need to do is to apply alternately 2 functions in a list. For example:
(*2) (-3) [4,5,6,7,8]
would result in
[8,2,12,4,16], because 4*2, 5-3, 6*2, 7-3, 8*2...
What I was thinking was
funct :: (a -> b) -> (a -> b) -> [a] -> [b]
and then something like
[f x | x <- xs] however I won't have just "f", but will have the other function as well.
So I was thinking about checking the position of x. If it is an even position, then f x. Otherwise g x.
Could someone help me?
Tks.
You do not really need the index, what you need is a list that alternates between (*2) and (-3). We can make use of cycle :: [a] -> [a] and zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]. We can thus use:
zipWith ($) (cycle [(2*), subtract 3]) [4,5,6,7,8]
Here ($) :: (a -> b) -> a -> b is used to perform a function application. So ($) f x is equivalent to f x.
This gives the expected:
Prelude> zipWith ($) (cycle [(2*), subtract 3]) [4,5,6,7,8]
[8,2,12,4,16]
You could always write this out with explicit recursion, to be completely clear about how it works:
alternateFunctions _ _ [] = []
alternateFunctions f g (x:xs) = f x:alternateFunctions g f xs
For an empty list, there's nothing to do. Otherwise, apply the first function to the first element, and recurse for the rest of the list with the first and second functions switched. With that, you have alternateFunctions (*2) (subtract 3) [4,5,6,7,8] = [8,2,12,4,16], as desired.
If you care about performance, then you should rewrite this as a foldr, so that it can fuse:
alternateFunctions f g xs = foldr go mempty xs f g
where go x acc f g = f x:acc g f

map function using foldl or foldr in Haskell

I am writing a function my_map which takes a unary function and a list and returns the list resulting from mapping the function over all elements of the input list.
Main> my_map (^3) [1..5]
[1,8,27,64,125]
I tried it like this:
my_map :: (a -> b) -> [a] -> [b]
my_map f [] = []
my_map f (x:xs) = foldr (\x xs -> (f x):xs) [] xs
But after running above, I get only [8,27,64,125]. the first number 1 is not displaying in output.
Can anybody help me?
You are using the (x:xs) pattern in your arguments, but when you apply the fold, you only apply it to the xs part, which means your first element i.e. the one that x represents never gets processed. You need to change it to this:
my_map :: (a -> b) -> [a] -> [b]
my_map f xs = foldr (\y ys -> (f y):ys) [] xs
Since you are using foldr, you do not need to explicitly handle the empty list case. Moreoever, you do not need to specify the list in (x:xs) format.
Finally, my own preference is to avoid using the same name for function inputs and any helper functions or expressions in the function definition.That is why, I have used xs for the input list and y and ys for the parameters passed to the lambda.
"shree.pat18" is perfectly right, and also the comments are valuable. I learned a lot from that. Just make it better visible, and to explain the alternatives...
Answer
-- The problem is here ....................... vv
my_map f (x:xs) = foldr (\x xs -> (f x):xs) [] xs
-- --
The remaining part xs is aplied to foldr.
To fix just this, apply the whole list. This can be done by placing xx# before (x:xs). By that, the whole list is bound to xx.
-- vvv ........... see here ............... vv
my_map f xx#(x:xs) = foldr (\x xs -> (f x):xs) [] xx
-- --- --
Recommended impovement
Note: foldr can already deal with [] as input. Hence, my_map f [] = [] is not needed. But foldr would not be called when you apply [] to my_map. To get rid of my_map f [] = [], you need to remove the pattern matching, because (x:xs) matches only to lists with at least one element.
main :: IO ()
main = print $ my_map (^(3 :: Int)) ([1..5] :: [Integer])
my_map :: (a -> b) -> [a] -> [b]
my_map f xx = foldr (\x xs -> (f x):xs) [] xx
The answer is complete here. The rest below is for pleasure.
Further reductions
Simple expression instead of lambda expression
If you want to reduce the lambda expression (\x xs -> (f x):xs), as suggested by "Aadit M Shah"...
(:) is equal to (\x xs -> x:xs), because : is an operator and its function is (:)
. can be used to combine the function f with (:), hence (\x xs -> (f x):xs) is equal to ((:) . f)
main :: IO ()
main = print $ my_map (^(3 :: Int)) ([] :: [Integer])
my_map :: (a -> b) -> [a] -> [b]
my_map f xx = foldr ((:) . f) [] xx
Currying
A function of the form
-- v v
f a b c = .... c
can be reduced to
-- v v
f a b = ....
and a function of the form
-- v v v v
f a b c = .... b c
can be reduced to
-- v v v v
f a = ....
and so on, by currying.
Hence, my_map f xx = foldr ((:) . f) [] xx equals my_map f = foldr ((:) . f) [].
Combination and flip
flip flips the first two parameters.
Example, the following functions are equal:
f' a b c = (\c' b' a' -> ((a' - b') / c')) b a c
f'' a b c = flip (\c' b' a' -> ((a' - b') / c')) a b c
f''' = flip (\c' b' a' -> ((a' - b') / c'))
Hence, the following code works as well.
main :: IO ()
main = print $ my_map (^(3 :: Int)) ([1..5] :: [Integer])
my_map :: (a -> b) -> [a] -> [b]
my_map f = flip foldr [] ((:) . f)
But we can not get rid of f as above, because of the form in the expression flip foldr [] ((:) . f).
If we remove f ...
`((:) . f)` has type `a -> [a] -> [a]
-- v
`((:) . )` has type `(a -> a) -> a -> [a] -> [a]`
and
`flip foldr []` has type `Foldable t => (a1 -> [a2] -> [a2]) -> t a1 -> [a2]`
hence
f :: a -> a
is passed to
((:) . )
becomming
a -> [a] -> [a]
is passed to
flip foldr []
becomming
t a1 -> [a2]
Hence,
main :: IO ()
main = print $ my_map (^(3 :: Int)) ([1..5] :: [Integer])
my_map :: (a -> b) -> [a] -> [b]
my_map = flip foldr [] . ((:) . )
works nicely.

Why can you reverse list with foldl, but not with foldr in Haskell

Why can you reverse a list with the foldl?
reverse' :: [a] -> [a]
reverse' xs = foldl (\acc x-> x : acc) [] xs
But this one gives me a compile error.
reverse' :: [a] -> [a]
reverse' xs = foldr (\acc x-> x : acc) [] xs
Error
Couldn't match expected type `a' with actual type `[a]'
`a' is a rigid type variable bound by
the type signature for reverse' :: [a] -> [a] at foldl.hs:33:13
Relevant bindings include
x :: [a] (bound at foldl.hs:34:27)
acc :: [a] (bound at foldl.hs:34:23)
xs :: [a] (bound at foldl.hs:34:10)
reverse' :: [a] -> [a] (bound at foldl.hs:34:1)
In the first argument of `(:)', namely `x'
In the expression: x : acc
Every foldl is a foldr.
Let's remember the definitions.
foldr :: (a -> s -> s) -> s -> [a] -> s
foldr f s [] = s
foldr f s (a : as) = f a (foldr f s as)
That's the standard issue one-step iterator for lists. I used to get my students to bang on the tables and chant "What do you do with the empty list? What do you do with a : as"? And that's how you figure out what s and f are, respectively.
If you think about what's happening, you see that foldr effectively computes a big composition of f a functions, then applies that composition to s.
foldr f s [1, 2, 3]
= f 1 . f 2 . f 3 . id $ s
Now, let's check out foldl
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t [] = t
foldl g t (a : as) = foldl g (g t a) as
That's also a one-step iteration over a list, but with an accumulator which changes as we go. Let's move it last, so that everything to the left of the list argument stays the same.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] t = t
flip (foldl g) (a : as) t = flip (foldl g) as (g t a)
Now we can see the one-step iteration if we move the = one place leftward.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] = \ t -> t
flip (foldl g) (a : as) = \ t -> flip (foldl g) as (g t a)
In each case, we compute what we would do if we knew the accumulator, abstracted with \ t ->. For [], we would return t. For a : as, we would process the tail with g t a as the accumulator.
But now we can transform flip (foldl g) into a foldr. Abstract out the recursive call.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) [] = \ t -> t
flip (foldl g) (a : as) = \ t -> s (g t a)
where s = flip (foldl g) as
And now we're good to turn it into a foldr where type s is instantiated with t -> t.
flip . foldl :: (t -> a -> t) -> [a] -> t -> t
flip (foldl g) = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)
So s says "what as would do with the accumulator" and we give back \ t -> s (g t a) which is "what a : as does with the accumulator". Flip back.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t))
Eta-expand.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = flip (foldr (\ a s -> \ t -> s (g t a)) (\ t -> t)) t as
Reduce the flip.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> \ t -> s (g t a)) (\ t -> t) as t
So we compute "what we'd do if we knew the accumulator", and then we feed it the initial accumulator.
It's moderately instructive to golf that down a little. We can get rid of \ t ->.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> s . (`g` a)) id as t
Now let me reverse that composition using >>> from Control.Arrow.
foldl :: (t -> a -> t) -> t -> [a] -> t
foldl g t as = foldr (\ a s -> (`g` a) >>> s) id as t
That is, foldl computes a big reverse composition. So, for example, given [1,2,3], we get
foldr (\ a s -> (`g` a) >>> s) id [1,2,3] t
= ((`g` 1) >>> (`g` 2) >>> (`g` 3) >>> id) t
where the "pipeline" feeds its argument in from the left, so we get
((`g` 1) >>> (`g` 2) >>> (`g` 3) >>> id) t
= ((`g` 2) >>> (`g` 3) >>> id) (g t 1)
= ((`g` 3) >>> id) (g (g t 1) 2)
= id (g (g (g t 1) 2) 3)
= g (g (g t 1) 2) 3
and if you take g = flip (:) and t = [] you get
flip (:) (flip (:) (flip (:) [] 1) 2) 3
= flip (:) (flip (:) (1 : []) 2) 3
= flip (:) (2 : 1 : []) 3
= 3 : 2 : 1 : []
= [3, 2, 1]
That is,
reverse as = foldr (\ a s -> (a :) >>> s) id as []
by instantiating the general transformation of foldl to foldr.
For mathochists only. Do cabal install newtype and import Data.Monoid, Data.Foldable and Control.Newtype. Add the tragically missing instance:
instance Newtype (Dual o) o where
pack = Dual
unpack = getDual
Observe that, on the one hand, we can implement foldMap by foldr
foldMap :: Monoid x => (a -> x) -> [a] -> x
foldMap f = foldr (mappend . f) mempty
but also vice versa
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f = flip (ala' Endo foldMap f)
so that foldr accumulates in the monoid of composing endofunctions, but now to get foldl, we tell foldMap to work in the Dual monoid.
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl g = flip (ala' Endo (ala' Dual foldMap) (flip g))
What is mappend for Dual (Endo b)? Modulo wrapping, it's exactly the reverse composition, >>>.
For a start, the type signatures don't line up:
foldl :: (o -> i -> o) -> o -> [i] -> o
foldr :: (i -> o -> o) -> o -> [i] -> o
So if you swap your argument names:
reverse' xs = foldr (\ x acc -> x : acc) [] xs
Now it compiles. It won't work, but it compiles now.
The thing is, foldl, works from left to right (i.e., backwards), whereas foldr works right to left (i.e., forwards). And that's kind of why foldl lets you reverse a list; it hands you stuff in reverse order.
Having said all that, you can do
reverse' xs = foldr (\ x acc -> acc ++ [x]) [] xs
It'll be really slow, however. (Quadratic complexity rather than linear complexity.)
You can use foldr to reverse a list efficiently (well, most of the time in GHC 7.9—it relies on some compiler optimizations), but it's a little weird:
reverse xs = foldr (\x k -> \acc -> k (x:acc)) id xs []
I wrote an explanation of how this works on the Haskell Wiki.
foldr basically deconstructs a list, in the canonical way: foldr f initial is the same as a function with patterns:(this is basically the definition of foldr)
ff [] = initial
ff (x:xs) = f x $ ff xs
i.e. it un-conses the elements one by one and feeds them to f. Well, if all f does is cons them back again, then you get the list you originally had! (Another way to say that: foldr (:) [] ≡ id.
foldl "deconstructs" the list in inverse order, so if you cons back the elements you get the reverse list. To achieve the same result with foldr, you need to append to the "wrong" end – either as MathematicalOrchid showed, inefficiently with ++, or by using a difference list:
reverse'' :: [a] -> [a]
reverse'' l = dl2list $ foldr (\x accDL -> accDL ++. (x:)) empty l
type DList a = [a]->[a]
(++.) :: DList a -> DList a -> DList a
(++.) = (.)
emptyDL :: DList a
emptyDL = id
dl2list :: DLList a -> [a]
dl2list = ($[])
Which can be compactly written as
reverse''' l = foldr (flip(.) . (:)) id l []
This is what foldl op acc does with a list with, say, 6 elements:
(((((acc `op` x1) `op` x2) `op` x3) `op` x4) `op` x5 ) `op` x6
while foldr op acc does this:
x1 `op` (x2 `op` (x3 `op` (x4 `op` (x5 `op` (x6 `op` acc)))))
When you look at this, it becomes clear that if you want foldl to reverse the list, op should be a "stick the right operand to the beginning of the left operand" operator. Which is just (:) with arguments reversed, i.e.
reverse' = foldl (flip (:)) []
(this is the same as your version but using built-in functions).
When you want foldr to reverse the list, you need a "stick the left operand to the end of the right operand" operator. I don't know of a built-in function that does that; if you want you can write it as flip (++) . return.
reverse'' = foldr (flip (++) . return) []
or if you prefer to write it yourself
reverse'' = foldr (\x acc -> acc ++ [x]) []
This would be slow though.
A slight but significant generalization of several of these answers is that you can implement foldl with foldr, which I think is a clearer way of explaining what's going on in them:
myMap :: (a -> b) -> [a] -> [b]
myMap f = foldr step []
where step a bs = f a : bs
-- To fold from the left, we:
--
-- 1. Map each list element to an *endomorphism* (a function from one
-- type to itself; in this case, the type is `b`);
--
-- 2. Take the "flipped" (left-to-right) composition of these
-- functions;
--
-- 3. Apply the resulting function to the `z` argument.
--
myfoldl :: (b -> a -> b) -> b -> [a] -> b
myfoldl f z as = foldr (flip (.)) id (toEndos f as) z
where
toEndos :: (b -> a -> b) -> [a] -> [b -> b]
toEndos f = myMap (flip f)
myReverse :: [a] -> [a]
myReverse = myfoldl (flip (:)) []
For more explanation of the ideas here, I'd recommend reading Tom Ellis' "What is foldr made of?" and Brent Yorgey's "foldr is made of monoids".

Zipping with padding in Haskell

A couple of times I've found myself wanting a zip in Haskell that adds padding to the shorter list instead of truncating the longer one. This is easy enough to write. (Monoid works for me here, but you could also just pass in the elements that you want to use for padding.)
zipPad :: (Monoid a, Monoid b) => [a] -> [b] -> [(a, b)]
zipPad xs [] = zip xs (repeat mempty)
zipPad [] ys = zip (repeat mempty) ys
zipPad (x:xs) (y:ys) = (x, y) : zipPad xs ys
This approach gets ugly when trying to define zipPad3. I typed up the following and then realized that of course it doesn't work:
zipPad3 :: (Monoid a, Monoid b, Monoid c) => [a] -> [b] -> [c] -> [(a, b, c)]
zipPad3 xs [] [] = zip3 xs (repeat mempty) (repeat mempty)
zipPad3 [] ys [] = zip3 (repeat mempty) ys (repeat mempty)
zipPad3 [] [] zs = zip3 (repeat mempty) (repeat mempty) zs
zipPad3 xs ys [] = zip3 xs ys (repeat mempty)
zipPad3 xs [] zs = zip3 xs (repeat mempty) zs
zipPad3 [] ys zs = zip3 (repeat mempty) ys zs
zipPad3 (x:xs) (y:ys) (z:zs) = (x, y, z) : zipPad3 xs ys zs
At this point I cheated and just used length to pick the longest list and pad the others.
Am I overlooking a more elegant way to do this, or is something like zipPad3 already defined somewhere?
How about custom head and tail functions (named next and rest in my example below)?
import Data.Monoid
zipPad :: (Monoid a, Monoid b) => [a] -> [b] -> [(a,b)]
zipPad [] [] = []
zipPad xs ys = (next xs, next ys) : zipPad (rest xs) (rest ys)
zipPad3 :: (Monoid a, Monoid b, Monoid c) => [a] -> [b] -> [c] -> [(a,b,c)]
zipPad3 [] [] [] = []
zipPad3 xs ys zs = (next xs, next ys, next zs) : zipPad3 (rest xs) (rest ys) (rest zs)
next :: (Monoid a) => [a] -> a
next [] = mempty
next xs = head xs
rest :: (Monoid a) => [a] -> [a]
rest [] = []
rest xs = tail xs
Test snippet:
instance Monoid Int where
mempty = 0
mappend = (+)
main = do
print $ zipPad [1,2,3,4 :: Int] [1,2 :: Int]
print $ zipPad3 [1,2,3,4 :: Int] [9 :: Int] [1,2 :: Int]
Its output:
[(1,1),(2,2),(3,0),(4,0)]
[(1,9,1),(2,0,2),(3,0,0),(4,0,0)]
This pattern comes up quite a lot. A solution I learned from Paul Chiusano is as follows:
data These a b = This a | That b | These a b
class Align f where
align :: (These a b -> c) -> f a -> f b -> f c
instance Align [] where
align f [] [] = []
align f (x:xs) [] = f (This x) : align f xs []
align f [] (y:ys) = f (That y) : align f [] ys
align f (x:xs) (y:ys) = f (These x y) : align f xs ys
liftAlign2 f a b = align t
where t (This l) = f l b
t (That r) = f a r
t (These l r) = f l r
zipPad a b = liftAlign2 (,) a b
liftAlign3 f a b c xs ys = align t (zipPad a b xs ys)
where t (This (x,y)) = f x y c
t (That r) = f a b r
t (These (x,y) r) = f x y r
zipPad3 a b c = liftAlign3 (,,) a b c
A little test in ghci:
*Main> zipPad3 ["foo", "bar", "baz"] [2, 4, 6, 8] [True, False] "" 0 False
[("foo",2,True),("bar",4,False),("baz",6,False),("",8,False)]
A simpler way to do this is with Maybe. I will illustrate with Edward's
more general formulation:
import Data.Maybe
import Control.Applicative
zipWithTails l r f as bs = catMaybes . takeWhile isJust $
zipWith fMaybe (extend as) (extend bs)
where
extend xs = map Just xs ++ repeat Nothing
fMaybe a b = liftA2 f a b <|> fmap l a <|> fmap r b
There are times when you want to be able to apply a different function to either tail rather than just supply mempty or manual zeroes as well:
zipWithTail :: (a -> a -> a) -> [a] -> [a] -> [a]
zipWithTail f (a:as) (b:bs) = f a b : zipWithTails f as bs
zipWithTail f [] bs = bs
zipWithTail f as _ = as
zipWithTails :: (a -> c) -> (b -> c) -> (a -> b -> c) -> [a] -> [b] -> [c]
zipWithTails l r f (a:as) (b:bs) = f a b : zipWithTails l r f as bs
zipWithTails _ r _ [] bs = fmap r bs
zipWithTails l _ _ as _ = fmap l as
I use the former when I'm doing something like zipWithTail (+)
and the former when I need to do something like zipWithTail (*b) (a*) (\da db -> a*db+b*da) since the former can be much more efficient than feeding a default into a function, and the latter a little bit so.
However, if you just wanted to make a more succinct version of what you have, you could probably turn to mapAccumL ,but its not any clearer, and the ++ can be expensive.
zipPad as bs = done $ mapAccumL go as bs
where go (a:as) b = (as,(a,b))
go [] b = ([],(mempty,b))
done (cs, both) = both ++ fmap (\x -> (x, mempty)) cs

Resources