Instead of starting with trying to pick up a map in some way, think about how to simplify and generalize the current function. Starting from this:
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct [] _ = [] dotProduct [(x,y)] z = [(x*z,y)] dotProduct ((x,y):xys) z = (x*z,y):dotProduct (xys) z
First, we rewrite the second case using the constructor (:) :
dotProduct ((x,y):[]) z = (x*z,y):[]
Expand [] as a result using the first case:
dotProduct ((x,y):[]) z = (x*z,y):dotProduct [] z
Comparing this with the third case, we see that they are identical, except that it is specialized for xys [] . So, we can simply completely eliminate the second case:
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct [] _ = [] dotProduct ((x,y):xys) z = (x*z,y):dotProduct (xys) z
Next, generalizing the function. First we rename it and let dotProduct name it:
generalized :: [(Float, Integer)] -> Float -> [(Float, Integer)] generalized [] _ = [] generalized ((x,y):xys) z = (x*z,y):generalized (xys) z dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct xs z = generalized xs z
First, we parameterize it with an operation specializing in multiplication for dotProduct :
generalized :: (Float -> Float -> Float) -> [(Float, Integer)] -> Float -> [(Float, Integer)] generalized _ [] _ = [] generalized f ((x,y):xys) z = (fxz,y):generalized f (xys) z dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct xs z = generalized (*) xs z
Further, we can observe two things: generalized no longer dependent on arithmetic, so it can work on any type; and the only time z used as the second argument of f , so we can combine them into one argument of the function:
generalized :: (a -> b) -> [(a, c)] -> [(b, c)] generalized _ [] = [] generalized f ((x,y):xys) = (fx, y):generalized f (xys) dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct xs z = generalized (* z) xs
Now note that f used only for the first element of the tuple. This sounds useful, so we will extract this as a separate function:
generalized :: (a -> b) -> [(a, c)] -> [(b, c)] generalized _ [] = [] generalized f (xy:xys) = onFirst f xy:generalized f (xys) onFirst :: (a -> b) -> (a, c) -> (b, c) onFirst f (x, y) = (fx, y) dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct xs z = generalized (* z) xs
Now we again notice that in generalized f used only with onFirst , so we again combine them into one function argument:
generalized :: ((a, c) -> (b, c)) -> [(a, c)] -> [(b, c)] generalized _ [] = [] generalized f (xy:xys) = f xy:generalized f (xys) dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct xs z = generalized (onFirst (* z)) xs
And once again, generalized no longer dependent on a list containing tuples, so we allow it to work with any type:
generalized :: (a -> b) -> [a] -> [b] generalized _ [] = [] generalized f (x:xs) = fx : generalized f xs
Now compare the code for generalized with this:
map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = fx : map f xs
It also turns out that there is also a slightly more general version of onFirst , so we will replace this and generalized with their standard library equivalents:
import Control.Arrow (first) dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)] dotProduct xs z = map (first (* z)) xs