63

I've searched for a half-hour, and still cannot figure it out.

In SIP: Modularizing Language Features there are a number of features which will require explicit "enabling" in Scala 2.10 (import language.feature). Amongst them there is postfixOps, to which I just cannot find a reference anywhere. What exactly does this feature allow?

Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
dmitry
  • 4,989
  • 5
  • 48
  • 72
  • Finally it seems that compiler compiles despite producing warnings. So, maybe one like me may just ignore those annoying warnings. – dmitry Oct 22 '12 at 13:52
  • I fear the plan for 2.11 onwards is that instead of just warnings, the result will be compile errors. As far as I understood it, the current state with the warnings is just to "ease" the transition... –  Jan 15 '13 at 01:31
  • That sucks, I still don't understand why `postfix ops` is in the same bucket with really powerful things like implicits or existential types. Tried to ask on SIP's page but no answer. – dmitry Jan 15 '13 at 14:01
  • 6
    Add -language:postfixOps to the scalac command line, and all is as it once was -- no import necessary, no warnings. In build.sbt just add that to your scalacOptions Seq. – AmigoNico Feb 10 '13 at 22:21
  • You should not use it in modern programs, don't suppress this warning, i described why. Please, check it out. – Alex Jun 25 '14 at 21:20

3 Answers3

63

It allows you to use operator syntax in postfix position. For example

List(1,2,3) tail

rather than

List(1,2,3).tail

In this harmless example it is not a problem, but it can lead to ambiguities. This will not compile:

val appender:List[Int] => List[Int] = List(1,2,3) ::: //add ; here
List(3,4,5).foreach {println}

And the error message is not very helpful:

    value ::: is not a member of Unit

It tries to call the ::: method on the result of the foreach call, which is of type Unit. This is likely not what the programmer intended. To get the correct result, you need to insert a semicolon after the first line.

Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
  • 7
    My god... I liked it so much and now I'm forced to place idiotic `import language.postfixOps` or use dots everywhere. Why this???!!! – dmitry Oct 22 '12 at 12:20
  • 2
    Actually this SIP 18 is the first thing in scala that really annoying and controversial. If I could image that structural typing and existentials, and even implicit conversions are used not very often in day to day code, but postfix syntax... I use it everywhere where possible. – dmitry Oct 22 '12 at 12:27
  • @dmitry: also, you don't have to add the imports to every file, you can just use a compiler switch and set it once for your whole project. – Kim Stebel Oct 22 '12 at 12:28
  • 4
    Are you sure that that example about postfix ops? I can easily provide example where it is useful. Say: `List(1,2,3) map { _ + 1 } reverse` is much readable (imho, of course) than `List(1,2,3).map( _ + 1).reverse` – dmitry Oct 22 '12 at 12:32
  • Yes, I am sure. Just try it. RC1 only gives you a warning though. – Kim Stebel Oct 22 '12 at 12:34
  • Yes, compiler switch is salvation, but it is annoying. If it is in build script noone ever new about it, but if I want just to compile a file I should provide those compiler keys manually. And everybody forgets such things. It means I always will get annoyed. And it is bad, because scala never annoyed me before. – dmitry Oct 22 '12 at 12:35
  • In your example there is no postfix operations. It is just ambiguous semantics in infix notations. Or where do I miss the thing? – dmitry Oct 22 '12 at 12:38
  • @dmitry: just compile it with 2.10.0-RC1 and you will see this warning: postfix operator ::: should be enabled [warn] by making the implicit value language.postfixOps visible. [warn] This can be achieved by adding the import clause 'import language.postfixOps' [warn] or by setting the compiler option -language:postfixOps. – Kim Stebel Oct 22 '12 at 12:45
  • Anyway, example is not about postfix. It is about dot which has higher precedence than method notation without dot. – dmitry Oct 22 '12 at 12:48
  • Would be nice (but even more confusing), if Scala had a lower precedence alternative for `.`. Then we might have `List(1,2,3) map (_ * 2) filter (_ != 2) $ reverse` and exchange a `$` for dots or parentheses. – Debilski Oct 22 '12 at 12:48
  • @dmitry: Of course it is about postfix. You can also fix the compiler error by changing the call to `:::` to dot syntax instead of postfix. Going to ignore further comments from you until their quality improves. – Kim Stebel Oct 22 '12 at 12:51
  • @kim-stebel I've done. No Warning. Just Error: `:7: error: value ::: is not a member of Unit`. And just because `List(3,4,5).foreach {println}` has been executed first and produced `Unit`. No postfix involved. – dmitry Oct 22 '12 at 12:52
  • @kim-stebel And in this stage I should say that despite your overall answer is correct, but example is quite confusing. And Régis Jean-Gilles is right, because postfix notation means "operator goes after the operands". And in scala it may be just if arg list is empty. In your example it is currying. And though it is produces warning (if ; added) it is obvious what is happening. Maybe better use wildcard for second argument? Btw, that example is really confusing. Because without ; and having enabled postfixOps it will produce the same error, just no warning. – dmitry Oct 22 '12 at 13:04
  • @Debilski Not sure I understand what you've meant by that $, but without $ `List(1,2,3) map (_ * 2) filter (_ != 2) reverse` works exactly as expected and produces `List(6, 4)` and without postfix ops it would be: `(List(1,2,3) map (_ * 2) filter (_ != 2)).reverse`? Is it better? – dmitry Oct 22 '12 at 13:09
  • 2
    @KimStebel With the ; example produces warning. But no error. And it is just a confusion of some different sort. Just because someone wants to curry methods without wildcard in place of parameter. This is barely sufficient explanation why postfixOps is a bad idea. – dmitry Oct 22 '12 at 13:26
  • 3
    +1 for the interesting example in appender. That should be the scalapuzzler. – som-snytt May 25 '13 at 01:16
36

The simplest answer ever:

Dropping dot from methods without parameters is DEPRECATED!

List(1,2,3) reverse //is bad style and will lead to unpredicted behaviour
List(1,2,3) map(_*2) reverse //bad too, because reverse can take first method call from the next line (details below)

OK to drop dot in methods that take one parameter of higher order function like map, filter, count and be safe! Also, purely functional methods like zip.

List(1,2,3) map(_*2) filter(_>2)
(List(1,2,3) map(_*2)).reverse //safe and good
List(1,3,5) zip List(2,4,6)

Long answer WHY

case class MyBool(x: Boolean) {
  def !!! = MyBool(!x) //postfix
  def or(other: MyBool): MyBool = if(x) other else this //infix
  def justMethod0() = this //method with empty parameters
  def justMethod2(a: MyBool, b: MyBool) = this //method with two or more
  override def toString = if(x) "true" else "false"
}

1) Postfix operator - is actually a method call with no parameters (a!==a.!) and without brackets. (considered not safe and deprecated)

val b1 = MyBool(false) !!!
List(1,2,3) head

2) Postfix operator is method, that should end the line, or else it will be treated as infix.

val b1 = MyBool(true) no! no! //ERROR
//is actually parsed like
val b2 = MyBool(true).no!(no!) //(no!) is unknown identifier
//as bad as
Vector(1,2,3) toList map(_*2) //ERROR

3) Infix operator is method with one parameter, that can be called without dot and parentheses. Only for purely functional methods

val c1 = MyBool(true) or b1 or MyBool(true)
val c2 = MyBool(true).or(b1).or(MyBool(true))
c1 == c2

4) Method with one or more parameters will chain without dot if you call it with parameters. def a(), def a(x), def a(x,y) But you should do this only for methods that use higher order function as parameter!

val d1 = MyBool(true) justMethod2(b1, c1) or b1 justMethod0() justMethod2(c1, b1)
//yes, it works, but it may be confusing idea
val d2 = MyBool(true).justMethod2(b1,c1).or(b1).justMethod0().justMethod2(c1, b1)
d1 == d2
//looks familiar? This is where it should be used:
List(1,2,3) filter(_>1) map(_*2)

Sample warnings:

warning: there were 1 deprecation warning(s); re-run with -deprecation for details warning: postfix operator tail should be enabled by making the implicit value scala.language.postfixOps visible. This can be achieved by adding the import clause 'import scala.language.postfixOps' or by setting the compiler option -language:postfixOps. See the Scala docs for value scala.language.postfixOps for a discussion why the feature should be explicitly enabled.

Alex
  • 592
  • 4
  • 10
5

It refers to the ability to call a nullary (with no arg list or empty arg list) method as a postfix operator:

By example:

case class MyBool(value: Boolean) {
    def negated = new MyBool(!value)
}
val b1 = MyBool( true )
val b2 = b1 negated // Same as b1.negated

See: http://www.scala-lang.org/node/118

Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
  • 1
    As my second example shows, it is not limited to nullary methods. – Kim Stebel Oct 22 '12 at 12:24
  • 1
    I am not sure to see what example you are refering to. Anyway, I guess that if you want to nitpick, this indeed will also work with non-nullary methods where all the parameters have default values. In any case, I was merely paraphrasing the offical scala documentation( see link): "As the first line of this code shows, it is also possible to use nullary methods as postfix operators". – Régis Jean-Gilles Oct 22 '12 at 12:35
  • 1
    No, there is no need for default values. I am referring to the example in my answer, specifically to the line `val appender:List[Int] => List[Int] = List(1,2,3) :::`. The `:::` method is not nullary and does not have default values. – Kim Stebel Oct 22 '12 at 12:36
  • 2
    Fair enough. I have checked, and it does seem like postfixOps encompasses this case too, even though to me they are quite different (your example shows how `myObject.myMethod` can silently be treated the same as `myObject.myMethod _`). – Régis Jean-Gilles Oct 22 '12 at 12:54
  • 1
    +1 for a comment section that's more engaging [to me] than the answer. – som-snytt May 25 '13 at 01:17