haskellpaddingtransposetype-variableszipwith

What do I lose by setting up zipWithPadding like this? (alternate zipWith in Haskell)


This problem came up when I was trying to zip through lists of different length and I didn't want them to be cut by the shortest one. It was in a context where the list had integers that I wanted to add or multiply. My problem is how to set up the type variables so that it can be more general.

zipWithPadding :: a -> (a -> a -> a) -> [a] -> [a] -> [a]
zipWithPadding n f = go n f
  where
    go n f [] a  = [f n x | x <- a]
    go n f a []  = [f n x | x <- a]
    go n f (x:xs) (y:ys) = f x y : go n f xs ys

Currently, because the element to pad the lists with has to be speficied, I have to write them this way, but it begs the question of whether it needs to be custom made for each context, or simply repurposed with higher order functions.

Edit: I would like to thank Chris in the comments for the help. It made the example at the bottom possible by changing the above solution into this:

zipPadWith :: a -> b -> (a -> b -> c) -> [a] -> [b] -> [c]
zipPadWith n m f = go n m f
  where
    go n _ f [] a          = [f n x | x <- a]
    go _ m f a []          = [f x m | x <- a]
    go n m f (x:xs) (y:ys) = f x y : go n m f xs ys

The example being:

transposeWith :: a -> [[a]] -> [[a]]
transposeWith n []       = []
transposeWith n (ms:mss) = zipPadWith n [n | _ <- mss] (:) ms (transposeWith n mss)

It transposes the list of lists without cutting content. It also pads with an element of choice so that it becomes rectangular. It won't work over an arbitrary number of lists inside another, because the latter is being used to count something, but the lists inside can be infinite.


Solution

  • If we implement zipPadWith in the following manner, the application of the default value as both arguments to f constrains the signature to a -> (a -> a -> a) -> [a] -> [a] -> [a].

    zipPadWith _ _ [] [] = []
    zipPadWith n f (x:xs) (y:ys) = f x y : zipPadWith n f xs ys
    zipPadWith n f (x:xs) [] = f x n : zipPadWith n f xs []
    zipPadWith n f [] (y:ys) = f n y : zipPadWith n f [] ys
    

    However, if the function takes two default arguments, one to use if the left list falls short, and one to use if the right list falls short, the type is not constrained, and can be a -> b -> (a -> b -> c) -> [a] -> [b] -> [c].

    zipPadWith _ _ _ [] [] = []
    zipPadWith n1 n2 f (x:xs) (y:ys) = f x y : zipPadWith n1 n2 f xs ys
    zipPadWith n1 n2 f (x:xs) [] = f x n2 : zipPadWith n1 n2 f xs []
    zipPadWith n1 n2 f [] (y:ys) = f n1 y : zipPadWith n1 n2 f [] ys