When to use parenthesis in Scala infix notation
Asked Answered
K

4

16

When programming in Scala, I do more and more functional stuff. However, when using infix notation it is hard to tell when you need parenthesis and when you don't.

For example the following piece of code:

def caesar(k:Int)(c:Char) = c match {
    case c if c isLower => ('a'+((c-'a'+k)%26)).toChar
    case c if c isUpper => ('A'+((c-'A'+k)%26)).toChar
    case _ => c
}

def encrypt(file:String,k:Int) = (fromFile(file) mkString) map caesar(k)_

The (fromFile(file) mkString) needs parenthesis in order to compile. When removed I get the following error:

Caesar.scala:24: error: not found: value map
    def encrypt(file:String,k:Int) = fromFile(file) mkString map caesar(k)_
                                                                 ^
one error found

mkString obviously returns a string on which (by implicit conversion AFAIK)I can use the map function.

Why does this particular case needs parentheses? Is there a general guideline on when and why you need it?

Kangaroo answered 8/4, 2011 at 8:45 Comment(0)
E
36

This is what I put together for myself after reading the spec:

  • Any method which takes a single parameter can be used as an infix operator: a.m(b) can be written a m b.
  • Any method which does not require a parameter can be used as a postfix operator: a.m can be written a m.

For instance a.##(b) can be written a ## b and a.! can be written a!

  • Postfix operators have lower precedence than infix operators, so foo bar baz means foo.bar(baz) while foo bar baz bam means (foo.bar(baz)).bam and foo bar baz bam bim means (foo.bar(baz)).bam(bim).
  • Also given a parameterless method m of object a, a.m.m is valid but a m m is not as it would parse as exp1 op exp2.

Because there is a version of mkString that takes a single parameter it will be seen as an infix opertor in fromFile(file) mkString map caesar(k)_. There is also a version of mkString that takes no parameter which can be used a as postfix operator:

scala> List(1,2) mkString
res1: String = 12

scala> List(1,2) mkString "a"
res2: String = 1a2

Sometime by adding dot in the right location, you can get the precedence you need, e.g. fromFile(file).mkString map { }

And all that precedence thing happens before typing and other phases, so even though list mkString map function makes no sense as list.mkString(map).function, this is how it will be parsed.

Eh answered 8/4, 2011 at 15:17 Comment(1)
Thanks, this helps to clarify!Kangaroo
I
5

The Scala reference mentions (6.12.3: Prefix, Infix, and Postfix Operations)

In a sequence of consecutive type infix operations t0 op1 t1 op2 . . .opn tn, all operators op1, . . . , opn must have the same associativity.
If they are all left-associative, the sequence is interpreted as (. . . (t0 op1 t1) op2 . . .) opn tn.

In your case, 'map' isn't a term for the operator 'mkstring', so you need grouping (with the parenthesis around 'fromFile(file) mkString')


Actually, Matt R comments:

It's not really an associativity issue, more that "Postfix operators always have lower precedence than infix operators. E.g. e1 op1 e2 op2 is always equivalent to (e1 op1 e2) op2". (Also from 6.12.3)

huynhjl's answer (upvoted) gives more details, and Mark Bush's answer (also upvoted) point to "A Tour of Scala: Operators" to illustrate that "Any method which takes a single parameter can be used as an infix operator".

Iroquois answered 8/4, 2011 at 10:39 Comment(5)
so fromFile(file) mkString map caesar(k)_ is in fact op1(t0) op2 op3 t4 but what is it interpreted as?Kangaroo
@Felix: I suspect op1(t0) op2(op3...) with op3 (i.e map) being incorrectly assimilated to a term instead of an operator.Iroquois
I suspected something like this. If you add a link to the scala reference I'll be glad to accept your answer :)Kangaroo
@Felix: link added, but you might wait a bit, in case of a different explanation from a true Scala specialist ;)Iroquois
It's not really an associativity issue, more that "Postfix operators always have lower precedence than infix operators. E.g. e1 op1 e2 op2 is always equivalent to (e1 op1 e2) op2". (Also from 6.12.3)Fallible
W
4

Here's a simple rule: never used postfix operators. If you do, put the whole expression ending with the postfix operator inside parenthesis.

In fact, starting with Scala 2.10.0, doing that will generate a warning by default.

For good measure, you might want to move the postfix operator out, and use dot notation for it. For example:

(fromFile(file)).mkString map caesar(k)_

Or, even more simply,

fromFile(file).mkString map caesar(k)_

On the other hand, pay attention to the methods where you can supply an empty parenthesis to turn them into infix:

fromFile(file) mkString () map caesar(k)_
Whiggism answered 8/4, 2011 at 19:1 Comment(5)
I don't like mixing the . (dot) notation with infix notation. Using the empty parens to turn mkString to a zero arity function is interesting, but this requires that the function is defined with parenthesis I guess.Kangaroo
@Kangaroo Actually, empty parenthesis on Scala 2.8 uses default arguments, I think.Whiggism
Daniel, I think I remember seeing mail on scala-lang recently about perhaps making it impossible to use postfix parameters without the dot in a future release. Or did I hallucinate that?Duet
@Duet You are correct. Starting with 2.10, it will be a warning. I don't think it will ever be forbidden, because it's very useful for DSLs, but it might be hidden behind the newly created language scheme.Whiggism
Ah, thanks -- that was it. And just to correct myself for the sake of other readers, I said postfix parameters but meant postfix operators. Lack of sleep...Duet
Z
2

The spec doesn't make it clear, but my experience and experimentation has shown that the Scala compiler will always try to treat method calls using infix notation as infix operators. Even though your use of mkString is postfix, the compiler tries to interpret it as infix and so is trying to interpret "map" as its argument. All uses of postfix operators must either be immediately followed by an expression terminator or be used with "dot" notation for the compiler to see it as such.

You can get a hint of this (although it's not spelled out) in A Tour of Scala: Operators.

Zygophyte answered 8/4, 2011 at 13:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.