Joe Walsh
Joe Walsh

Reputation: 1

Type error with function using tuples and lists

The problem code follows below.

type Point = (Float, Float)
xs = []
arrange_in_pairs :: Point -> Point -> [Point] -> [(Point, Point)]
arrange_in_pairs (x, y) (xi, yi) points 
    | length xs == 0 = xs ++ [((x, y), (points !! 0))]
    | (length xs - 1) /= length points = xs ++ [((points !! (length xs - 1)), (points !! length xs))] arrange_in_pairs (x, y) (xi, yi) points
    | otherwise = xs ++ [((points !! length xs), (xi, yi))] xs

The idea is a point is a co-ordinate like on a graph. (x, y) is the starting point, (xi, yi) is the end point and "points" is a list of points in between them. The function is supposed to take each pair of points as a tuple and store them in a list. so the first tuple is (x, y) and the first point in "points". the second tuple is the first point in "points" and the second point in "points" and so on until the last tuple in the list is the last point in "points" and (xi, yi). Any help really appreciated.

the error im getting is:

*** Expression     : [(points !! (length xs - 1),points !! length xs)] arrange_in_pairs (x,y) (xi,yi) points
*** Term           : [(points !! (length xs - 1),points !! length xs)]
*** Type           : [((Float,Float),(Float,Float))]
*** Does not match : a -> b -> c -> d -> e

Upvotes: 0

Views: 123

Answers (2)

DarthFennec
DarthFennec

Reputation: 2768

The error you're getting is because of this line:

[((points !! (length xs - 1)), (points !! length xs))] arrange_in_pairs (x, y) (xi, yi) points

When you put spaces between things in Haskell, it means you're calling a function. In this case, the first part of the line:

[((points !! (length xs - 1)), (points !! length xs))]

is being interpreted as a function, being called with four arguments:

arrange_in_pairs
(x, y)
(xi, yi)
points

This is why it says Does not match : a -> b -> c -> d -> e. It's expecting a function that takes four arguments and returns a value, but instead it sees a value of type [((Float,Float),(Float,Float))].


I think what you're trying to do here is use xs ++ [...] to update the value of xs, and then return a value arrange_in_pairs (x, y) (xi, yi) points, perhaps? There are a number of things wrong with this:

  • As stated, just putting a space between the two pieces of code doesn't work. Haskell interprets that as function application.
  • The ++ operator does not update values, it simply concatenates two lists and returns the result. xs does not change if you do this.
  • Indeed, in Haskell all variables are immutable, so if you define an xs = [] in the global scope it will never have any value other than []. You need to use a different approach, such as recursion.
  • The value you're returning is a recursive call with unaltered arguments. This is equivalent to a loop that never increments its counter: it will loop forever and never get any work done.

@JorgeAdriano mentioned a more concise solution, but I don't think it really helps much with understanding the principles. If I were to do this problem, my approach would be to try a recursive function. To do this, you need two things:

  • A base case: What do we return if points is empty?
  • A recursive case: We can use the first element of points to decide what the first element of the result should be, and we can also call the function again with different arguments so that it gives us the rest of the output.

Recursive functions over lists depend on the cons (:) constructor. I bring this up because I notice you don't use it in your code, but it's quite important:

  • x:xs returns a list where the first element is x, and the rest of the elements are the elements in the list xs.
  • When used as an argument (in a pattern match), x:xs binds the first element of the list argument to x, and the rest of the elements to the list xs. If the list is empty, x:xs does not match.
  • When used as an argument (in a pattern match), [] matches only if the list is empty.

The base case (points is empty) is that a single segment from the starting to the ending point is returned. This can be written:

arrange_in_pairs start end [] = [(start, end)]

The recursive case is more complicated. We know the first element needs to be a segment from the starting point to the first element in points, so we can begin with this:

arrange_in_pairs start end (x:xs) = (start, x) : ???

Instead of binding the third argument to points, we're binding its first element to x and the rest to the list xs. The return value is the first element (a segment from start to x), consed to the rest of the elements (a list we haven't defined yet).

So far, so good. The ??? should be a recursive call to arrange_in_pairs, and it should be called in such a way that the first segment it returns is the next segment we need. The next segment we need is from x to the first element of xs, so we need to pass x in place of start and xs in place of points:

arrange_in_pairs start end (x:xs) = (start, x) : arrange_in_pairs x end xs

The full function looks like this:

arrange_in_pairs :: Point -> Point -> [Point] -> [(Point, Point)]
arrange_in_pairs start end [] = [(start, end)]
arrange_in_pairs start end (x:xs) = (start, x) : arrange_in_pairs x end xs

Upvotes: 1

Hint:

so the first tuple is (x, y) and the first point in "points". the second tuple is the first point in "points" and the second point in "points" and so on until the last tuple in the list is the last point in "points" and (xi, yi).

There is a function zip with type,

zip :: [a] -> [b] -> [(a, b)]

which pairs the elements of two lists position-wise. Also you can build lists,

list1 = [(x, y)] ++ points  
list2 = points ++ [(xi,yi)]

once you have that, you're really close.

Upvotes: 1

Related Questions