Pergunta

I was working on a project last night, and had some code like this:

/* fixes warnings in 2.10 */
import scala.language.implicitConversions

/* had some kind of case class with some members */
case class Wrapper[A](x: A)

/* for convenience's sake */
implicit def aToWrapper[A](x: A) = Wrapper(x)

/* had some kind of Seq */
val xs: List[Wrapper[String]] =  List("hello", "world", "this", "is", "a", "test")

I then accidentally wrote:

xs foldLeft("") { (a, x) => a + x }

having left the . out between xs and foldLeft.

The types in question were a little more complex, and it asked me to annotate the lambda's parameter types, so I quickly did, thinking that was the source of my mistake. I ended up with something like this:

xs foldLeft("") { (a: String, x: Wrapper[String]) => a + x }

At this point I was receiving the error:

<console>:13: error: type mismatch;
found   : (String, Wrapper[String]) => String
required: Int
           xs foldLeft("") { (a: String, x: Wrapper[String]) => a + x }
                                                            ^

Obviously the fix was xs.foldLeft("") ..., but I have been wondering why the compiler is expecting an Int in this case. Could anyone illuminate how this is being parsed? this has been nagging me all day.

Foi útil?

Solução

When you leave out the dot and the parentheses you use the so called infix notation. It allows you to write a + b instead of a.+(b). An important rule here is that this is only allowed if a call is of the form object method paramlist (see SLS 6.12.3):

The right-hand operand of a left-associative operator may consist of several arguments enclosed in parentheses, e.g. e op (e 1 , ... , e n ). This expression is then interpreted as e.op(e 1 , ... , e n ).

foldLeft doesn't fit into this form, it uses object method paramlist1 paramlist2. Thus if you write this in operator notation the compiler treats it as object.method(paramlist1).paramlist2 (as described in SLS 6.12.2):

A postfix operator can be an arbitrary identifier. The postfix operation e op is interpreted as e.op.

But there is another rule applied here: Function Applications (SLS 6.6).

An application f(e 1 , ... , e m) applies the function f to the argument expressions e 1 , ... , e m.

[...]

If f has some value type, the application is taken to be equivalent to f.apply(e 1 , ... , e m), i.e. the application of an apply method defined by f.

Here we go:

scala> { (a, x) => a + x }
<console>:12: error: missing parameter type
              { (a, x) => a + x }
                 ^
<console>:12: error: missing parameter type
              { (a, x) => a + x }
                    ^

This is just a function literals that misses its type arguments. If we add them everything compiles fine:

scala> { (a: String, x: Wrapper[String]) => a + x }
res6: (String, Wrapper[String]) => String = <function2>

The compiler just applies the rule about function applications described above:

scala> "" { (a: String, x: Wrapper[String]) => a + x }
<console>:13: error: type mismatch;
 found   : (String, Wrapper[String]) => String
 required: Int
              "" { (a: String, x: Wrapper[String]) => a + x }
                                                   ^

scala> "" apply { (a: String, x: Wrapper[String]) => a + x }
<console>:13: error: type mismatch;
 found   : (String, Wrapper[String]) => String
 required: Int
              "" apply { (a: String, x: Wrapper[String]) => a + x }
                                                         ^

Thus, your code is interpreted as

scala> xs foldLeft ("").apply{ (a: String, x: Wrapper[String]) => a + x }
<console>:14: error: type mismatch;
 found   : (String, Wrapper[String]) => String
 required: Int
              xs foldLeft ("").apply{ (a: String, x: Wrapper[String]) => a + x }
                                                                      ^

But why does it apply the functions applications rule? It would also be possible to apply the function literal as postfix operator. To find out why we get the shown error message wen need to look at SLS Scala Syntax Summary. There we can see the following:

  InfixExpr         ::=  PrefixExpr
                      |  InfixExpr id [nl] InfixExpr
  PrefixExpr        ::=  [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr 
  SimpleExpr        ::=  ‘new’ (ClassTemplate | TemplateBody)
                      |  BlockExpr
                      |  SimpleExpr1 [‘_’]
  SimpleExpr1       ::=  Literal
                      |  Path
                      |  ‘_’
                      |  ‘(’ [Exprs] ‘)’
                      |  SimpleExpr ‘.’ id 
                      |  SimpleExpr TypeArgs
                      |  SimpleExpr1 ArgumentExprs
                      |  XmlExpr
  Exprs             ::=  Expr {‘,’ Expr}
  ArgumentExprs     ::=  ‘(’ [Exprs] ‘)’
                      |  ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ‘)’
                      |  [nl] BlockExpr

From the sections described above we know that ArgumentExprs describes function application while InfixExpr describes an infix expression. Because of the rules of EBNF, the most upper rule has the lowest precedence. And because the former rule is called by the latter one, it means that the function literal is applied before the infix expression, hence the error message.

Outras dicas

I believe you can only use binary operators as infix notation. I think you can also remedy the situation by using parenthesis: (xs foldLeft ("")) { (a: String, x: Wrapper[String]) => a + x }. Probably, it parses your original code as xs.foldLeft("").{ (a: String, x: Wrapper[String]) => a + x }. Check out this answer: When to use parenthesis in Scala infix notation

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top