4

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.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
WKQ
  • 53
  • 1
  • 5
  • 6
    About half the code in the version you gave is just *reimplementing* `map`. Do you understand what `map` does? Have you looked at its source code? – C. A. McCann Aug 23 '11 at 18:33
  • Well, i have done a few simple elemental problems using Map... And that's all i know about it. So you're right, i need a deeper knowledge of it. – WKQ Aug 23 '11 at 18:58
  • I certainly encourage it. Most of the standard library functions in Haskell are very simple and easy to comprehend if you look at their implementations. Hopefully the worked example in my answer will help with understanding `map` a bit better. – C. A. McCann Aug 23 '11 at 19:02
  • How about dotProduct x = map $ first (*x) – fuz Aug 24 '11 at 05:47

3 Answers3

13

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
C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • 1
    I'm amazed at your explanation!! I will read it carefully. Thank you very much for taking the trouble to write an answer like that! – WKQ Aug 23 '11 at 19:24
  • I second that! As a beginner in Haskell I'm so thankful for deep explanations like this! Much appreciated! – marvinhagemeister Feb 25 '17 at 08:57
4
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
Drekembe
  • 2,620
  • 2
  • 16
  • 13
  • 1
    If you're answering a question that's explicitly *homework*, is it *really* so much to ask that you at least *explain* the answer, rather than just dropping a line of code and nothing else? – C. A. McCann Aug 23 '11 at 18:34
  • Thank you very much!! I always have troubles with lambda abstraction... And yes, the name "dotProduct" does not fit very well to the problem, but that is what is supposed to do. (Sorry for my English) – WKQ Aug 23 '11 at 18:38
2

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.

Antti
  • 11,944
  • 2
  • 24
  • 29