views:

150

answers:

4

Hello all,

I have been working on a project in scala, but I am getting some error messages that I don't quite understand. The classes that I am working with are relatively simple. For example:

abstract class Shape
case class Point(x: Int, y: Int) extends Shape
case class Polygon(points: Point*) extends Shape

Now suppose that I create a Polygon:

val poly = new Polygon(new Point(2,5), new Point(7,0), new Point(3,1))

Then if I attempt to determine the location and size of the smallest possible rectangle that could contain the polygon, I get various errors that I don't quite understand.

Below are snippets of different attempts and the corresponding error messages that they produce.

val upperLeftX = poly.points.reduceLeft(Math.min(_.x, _.x))

Gives the error:
"missing parameter type for expanded function ((x$1) => x$1.x)"

val upperLeftX =  
         poly.points.reduceLeft((a: Point, b: Point) => (Math.min(a.x, b.x)))

Gives this error:
"type mismatch;
found : (Point, Point) => Int
required: (Any, Point) => Any
"

I am very confused about both of these error messages. If anyone could explain more clearly what I am doing incorrectly, I would really appreciate it. Yes, I see that the second error says that I need type "Any" but I don't understand exactly how to implement a change that would work as I need it. Obviously simply changing "a: Point" to "a: Any" is not a viable solution, so what am I missing?

+2  A: 

This is Scala 2.8.0.RC2

scala> abstract class Shape
defined class Shape

scala> case class Point(x: Int, y: Int) extends Shape
defined class Point

scala> case class Polygon(points: Point*) extends Shape
defined class Polygon

scala> val poly = new Polygon(new Point(2,5), new Point(7,0), new Point(3,1))
poly: Polygon = Polygon(WrappedArray(Point(2,5), Point(7,0), Point(3,1)))

scala> val upperLeftX = poly.points.reduceLeft((a:Point,b:Point) => if (a.x < b.x) a else b)
upperLeftX: Point = Point(2,5)

reduceLeft requires here a function of the type (Point, Point) => Point. (more precisely (B, Point) => B with B with a lower bound to Point. See Scaladoc at the method reduceLeft.

michael.kebe
This returns a Point, however in my examples I am attempting to return an Int (the smallest x-coordinate to be precise). And I believe my problem lies is somehow due to the compiler getting confused as to which type it should be returning.altering your code to return an Int (which is what I need): val upperLeftX = poly.points.reduceLeft((a:Point,b:Point) => if (a.x < b.x) a.x else b.x) gives me the same error message
klactose
Simply try this one then: `val upperLeftX = poly.points.reduceLeft((a,b) => if (a.x < b.x) a else b).x` See the appending `.x`
michael.kebe
for scala2.8:val upperLeftX = poly.points.map(_.x).min
Eastsun
Hey Michael, That worked! Thanks. Still no clue why though...
klactose
+6  A: 

The type of reduceLeft is reduceLeft[B >: A](op: (B, A) => B): B, A is Point, and you are trying to apply it to (a: Point, b: Point) => (Math.min(a.x, b.x)).

The compiler reasons thus: Math.min(a.x, b.x) returns Int, so Int must be a subtype of B. And B must also be a supertype of Point. Why? B is the type of the accumulator, and its initial value is the first Point in your Polygon. That's the meaning of B >: A.

The only supertype of Int and Point is Any; so B is Any and the type of op should be (Any, Point) => Any, just as the error message says.

Alexey Romanov
Ok... I think that I actually understood that. Thanks for the explanation!
klactose
+2  A: 

Another alternative is poly.points.foldLeft(Int.MaxValue)((b, a) => Math.min(b, a.x)), which should also work with Scala 2.7.x. The differences compared to the reduceLeft version are

  • you have a start value (Int.MaxValue in our case, any real data will be smaller or equal to this)
  • there are no constraints between the type of the elements and the type of the result, like the lower bound constraint for reduceLeft Nevertheless Eastsun's solution is more elegant.

BTW if you already have case classes you can ommit the new keyword, and can use the automatically generated factory method in the companion object. So the line creating the poly becomes val poly = Polygon(Point(2,5), Point(7,0), Point(3,1)), which is a bit easier to read.

Sandor Murakozi
thanks, I'll keep that info about the new keyword in mind
klactose
@Rahul: thanks for the correction. I was shortly looking for that, but could not find it :-)
Sandor Murakozi
A: 

I see everyone seems to have latched onto the second snippet, so I'll answer the first one:

val upperLeftX = poly.points.reduceLeft(Math.min(_.x, _.x))

You intended that to mean this:

val upperLeftX = poly.points.reduceLeft((a, b) => Math.min(a.x, b.x))

However, that's not how underscore works. There are many meanings to underscore, but two of them are relevant here.

First, it may mean a partial function application. For example, Math.min(_, 0) would partially apply the parameters to min, and return a function that apply the remaining ones. In other words, it is equivalent to x => Math.min(x, 0), ignoring the type annotations. At any rate, this meaning only applies if the underscore is all by itself in the place of one (or more) of the parameters.

That, however, is not the case in your example, because you added a .x after the underscore. If the underscore appears in any kind of expression, such as the method call in your example, then that underscore is a placeholder for a parameter in an anonymous function.

In this second meaning it is particularly important to understand the boundaries of the anonymous function. Specifically, the anonymous function will be delimited by the innermost parenthesis or curly brackets that encloses it, or by any comma.

Now, applying that rule to the expression in the first snippet means that snipper is seen by the compiler like this:

val upperLeftX = poly.points.reduceLeft(Math.min(a => a.x, b => b.x))

So, there are two problems here. First, you are passing two functions to min instead of two doubles. Second, because min isn't expecting to receive functions, the compiler can't infer what the type of these functions might be. Since you did not provide any information about the types of a and b above, it complains about that.

If you did provide such types, the error message would be something like this:

<console>:6: error: type mismatch;
 found   : Int
 required: ?{val x: ?}
Daniel