Reputation: 53
I have to write a Haskell program that does the following:
Main> dotProduct [(1,3),(2,5),(3,3)] 2
[(2,3),(4,5),(6,3)]
I have to do it both with and without map
function.
I already did it without map
, but I have no clue to do it with map
.
My dotProduct
without map
function:
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
So I really need help with the map
version.
Upvotes: 4
Views: 7981
Reputation: 77374
Rather than starting by trying to fit map
in somehow, consider how you might simplify and generalize your 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'll rewrite the second case using the (:)
constructor:
dotProduct ((x,y):[]) z = (x*z,y):[]
Expanding the []
in the result using the first case:
dotProduct ((x,y):[]) z = (x*z,y):dotProduct [] z
Comparing this to the third case, we can see that they're identical except for this being specialized for when xys
is []
. So, we can simply eliminate the second case entirely:
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
call 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 by the operation, specializing to multiplication for dotProduct
:
generalized :: (Float -> Float -> Float) -> [(Float, Integer)] -> Float -> [(Float, Integer)]
generalized _ [] _ = []
generalized f ((x,y):xys) z = (f x z,y):generalized f (xys) z
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (*) xs z
Next, we can observe two things: generalized
doesn't depend on arithmetic directly anymore, so it can work on any type; and the only time z
is used is as the second argument to f
, so we can combine them into a single function argument:
generalized :: (a -> b) -> [(a, c)] -> [(b, c)]
generalized _ [] = []
generalized f ((x,y):xys) = (f x, y):generalized f (xys)
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (* z) xs
Now, we note that f
is only used on the first element of a tuple. This sounds useful, so we'll extract that 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) = (f x, y)
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (* z) xs
Now we again observe that, in generalized
, f
is only used with onFirst
, so we again combine them into a single 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, we observe that generalized
no longer depends on the list containing tuples, so we let it work on any type:
generalized :: (a -> b) -> [a] -> [b]
generalized _ [] = []
generalized f (x:xs) = f x : generalized f xs
Now, compare the code for generalized
to this:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
It also turns out that a slightly more general version of onFirst
also exists, so we'll replace both that and generalized
with their standard library equivalents:
import Control.Arrow (first)
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = map (first (* z)) xs
Upvotes: 13
Reputation: 12479
EEVIAC already posted the answer, so I'll just explain how to come up with it yourself. As you probably know, map
has the type signature (a -> b) -> [a] -> [b]
. Now, dotProduct
has the type [(Float, Integer)] -> Float -> [(Float, Integer)]
and you'll call map
somewhere in there, so it has to look something like this:
dotProduct theList z = map (??? z) theList
where ???
is a function of type Float -> (Float, Integer) -> (Float, Integer)
- this follows immediately from the type signature of map
and from the fact that we pass z
to the function, which we have to do, simply because there's no other place to use it in.
The thing with map
and higher order functions in general is that you have to keep in mind what the higher order function does and "simply" supply it with the correct function. As map
applies a given function to all elements in the list, your function only needs to work with one element, and you can forget all about the list - map
will take care of it.
Upvotes: 2
Reputation: 2716
dotProduct xs z = map (\(x,y) -> (x*z,y)) xs
The (\(x,y) -> (x*z,y))
part is a function which takes a pair and returns a new pair that's like the old one, except its first component is multiplied by z
. The map
function takes a function and applies it to each element in a list. So if we pass the (\(x,y) -> (x*z,y))
function to map
, it will apply that function to every element in xs
.
Although are you sure your first one is correct? The dot product operation is usually defined so that it takes two vectors, multiplies corresponding component and then sums it all together. Like this:
dotProduct xs ys = sum $ zipWith (*) xs ys
Upvotes: 4