Understanding infix method call and cons operator(::) in Scala
Asked Answered
J

2

21

I'm quite new to Scala programming language, and was trying something out stucked in my mind while I was following the lecture notes at here.

I think I couldn't really understand how cons operator works, here are some things I tried:

I've created a pseudo-random number generator, then tried to create a list of one random value:

scala> val gen = new java.util.Random
gen: java.util.Random = java.util.Random@1b27332

scala> gen nextInt 3 :: Nil
<console>:7: error: type mismatch;
 found   : List[Int]
 required: Int
       gen nextInt 3 :: Nil
                     ^

But it tried to pass List(3) to nextnt method. When i used paratheses, there was no problem

scala> (gen nextInt 3) :: Nil
res69: List[Int] = List(1)

I was curious about the execution order, so i created a function to check it

scala> def pr(i:Int):Int = { println(i); i }
pr: (i: Int)Int

scala> pr(1) :: pr(2) :: pr(3) :: Nil
1
2
3
res71: List[Int] = List(1, 2, 3)

As seen in outputs, execution order is the same as the order of appearance. Then I thought it might be about the 'nextInt' function, then I tried following:

scala> 1 + 2 :: Nil
res72: List[Int] = List(3)

It first executed addition, and after that cons is executed. So here is the question: What is the difference between gen nextInt 3 :: Nil and 1 + 2 :: Nil?

Jocko answered 5/7, 2010 at 19:26 Comment(0)
W
44

There are two things of concern here: precedence and fixity. As sepp2k mentioned, this question on Stack Overflow explains the precedence, thought the rules, as quoted, are not complete enough, and there were very small changes from Scala 2.7 to Scala 2.8. Differences concern mostly operators ending in =, though.

As for fixity, almost everything in Scala is read left to right, which is what programmers are used to. In Scala, however, operators ending in : are read right to left.

Take, then, this example:

1 + 2 :: Nil

First, precedence. What has most precedence, + or :? According to the table, + has precedence over :, so the addition is done first. Therefore, the expression is equal to this:

((1).+(2)) :: Nil

Now there's no precedence conflict, but since :: ends in :, it has a diferent fixity. It is read right to left, therefore:

Nil.::((1).+(2))

On the other hand, in this:

gen nextInt 3 :: Nil

The operator :: has precedence over nextInt, because : has precedence over all letters. Therefore, and remembering its fixity, it becomes:

gen nextInt Nil.::(3)

Which then becomes

gen.nextInt(Nil.::(3))

At which point the error is obvious.

PS: I'm writing (1).+(2) instead of 1.+(2) because, at the time of this writing, 1. is interpreted as a double number, making 1.+(2) an infix expression adding the double 1.0 to 2. This syntax is deprecated as of Scala 2.10.0, and will probably not be present on Scala 2.11.

Waddell answered 5/7, 2010 at 20:58 Comment(7)
Thank you for your detailed answering, it gave me many clues about many other things, too. But I have one more question: How does the compiler interpret the expression in the third code section I gave in my initial question; in terms of parenthesis? because using function pr showed that the terms are executed in the order from left to right.Jocko
That correct, the function calls are evaluated from left to right. That is because pr(X) are the argument expressions of :: and these are evaluated first in order of appearance and then the value is passed to the method. pr(1) :: pr(2) :: pr(3) :: Nil prints 1 2 3. But Nil.::(pr(3)).::(pr(2)).::(pr(1)) prints 3 2 1. But both are returning List(1, 2, 3)Fern
@Jocko I don't know. It might be that operator notation with right fixity enables an evaluation order not possible with dot notation, or it might be that the compiler optimizes :: calls but has a bug concerning evaluation order.Waddell
Thanks for responding. I'll investigate further and if I find something I'll post here.Jocko
@DanielC.Sobral In your example should you not have a space before the dot. Should it not be: (1 .+(2)) :: Nil Otherwise you are converting 1 to a double.Fjord
@Fjord True enough, fixed. Though the next major version of Scala most likely won't have that problem.Waddell
"In Scala, however, operators ending in : are read right to left." Are you kidding me? I feel like the Scala designers' fear of hardcoding rules for specific operators isn't very consistent with the reality of having to deal with the baffling variety of operator rules anyway.Neuropsychiatry
O
3

It's about precedence not execution order. + has higher precedence than ::, so a + b :: c parses as (a + b) :: c. However infix method calls with regular names have lower precedence, so a foo b c parses as a foo (b c).

See this question for a list of operators ordered by their precedence in scala.

Opportunism answered 5/7, 2010 at 19:37 Comment(1)
Thanks for your explanation. Precedence was what I just missed to think of.Jocko

© 2022 - 2024 — McMap. All rights reserved.