Need help figuring out scala compiler errors
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 开发者_如何学JAVAhow 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?
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.
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
.
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.
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: ?}
精彩评论