linkhyrule5
linkhyrule5

Reputation: 918

Difference between dot and space in Scala

What precisely is the difference between . and when used to invoke functions from objects in Scala?

For some reason, I get variations, like:

scala> val l:List[Int] = 1::Nil
l: List[Int] = List(1, 2, 3)

scala> l foldLeft(0)((hd, nxt) => hd + nxt) 
<console>:13: error: Int(1) does not take parameters
       | foldLeft(1)((hd, nxt) => hd + nxt)
                    ^
scala>l.foldLeft(0)((hd, nxt) => hd + nxt)
res2: Int = 2

(And while I'm at it, what's the name of that operation? I kept trying to find the strict definition of the . operator and I have no idea what it's called.)

Upvotes: 4

Views: 2467

Answers (2)

Michael Lafayette
Michael Lafayette

Reputation: 3072

Desugar it with "-Xprint:parser" or "-Xprint:typer"

Example 1 Desugared:

scala> (List(1,2) foldLeft 0)((hd, nxt) => hd + nxt) 
...
List(1, 2).foldLeft(0)(((hd, nxt) => hd.$plus(nxt)))
...
immutable.this.List.apply[Int](1, 2).foldLeft[Int](0)(((hd: Int, nxt: Int) => hd.+(nxt)));

As you can see, (List(1,2) foldLeft 0) translates into (List(1, 2).foldLeft(0)) in the parser phase. This expression returns a curried function that takes in the second set of parenthesis to produce a result (remember that a curried function is just a function that takes in an argument and returns another function with one fewer argument).

Example 2 Desugared:

scala> List(1,2) foldLeft(0)((hd, nxt) => hd + nxt) 
...
List(1, 2)(foldLeft(0)(((hd, nxt) => hd.$plus(nxt))))
...
<console>:8: error: not found: value foldLeft
          List(1,2) (foldLeft(0)((hd, nxt) => hd + nxt))

The parenthesis are going around (foldLeft(0)((hd, nxt) => hd + nxt)).

Style:

The way you are supposed to use space delimited methods is 1 object followed by 1 method followed by 1 set of parenthesis, which produces a new object that can be followed by a new method.

obj method paramerer // good

obj method1 paramerer1 method2 paramerer2 // good

obj method1 paramerer1 method2 paramerer2 method3 paramerer3 // compiles, but might need to be broken up

You can follow an object with postfix a method that takes no parameters, but this isn't always the approved style, especially for accessors.

foo.length // good

foo length // compiles, but can be confusing.

Space delimited methods are normally reserved for either pure functions (like map, flatmap, filter) or for domain specific languages (DSL).

In the case of foo.length, there is no () on length, so the whitespace isn't necessary to convey the idea that length is pure.

Upvotes: 0

V-Lamp
V-Lamp

Reputation: 1678

Having space instead of dot is called postfix notation if there are no arguments in the called function on the object, or infix notation if there is an argument that the function requires.

Postix example: l sum, equivalent to l.sum

Infix example: l map (_ * 2), equivalent to l.map(_ * 2)

The issue with these notations is that they are inherently more ambiguous in their interpretation. A classic example from math:

  • 1 + 2 * 3 + 4 is ambiguous and depends on the priority of the operators.
  • 1.+(2.*(3).+(4) has only one meaningful interpretation.

Therefore it is not a different operator, but the same as the dot, just susceptible to ambiguity that can lead to syntactical errors like your case or even worse logical errors when you chain infix operators.

You can actually express foldLeft with infix notation in this way:

(l foldLeft 0)((hd, nxt) => hd + nxt) 

or even

(0 /: l)((hd, nxt) => hd + nxt)

Where /: is just an alias for foldLeft and makes use of the unique semantics of operator ending in colon(:), which are interpreted as l./:(0) (the reverse of the usual).

Upvotes: 7

Related Questions