Scala's "postfix ops"

35,516

Solution 1

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.

Solution 2

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.

Solution 3

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

Share:
35,516
dmitry
Author by

dmitry

Application developer. Team leader. Favourite languages are Scala, Python, Elixir, Ruby. Fluent in PHP, Java, Javascript, R, Matlab/Octave. Used a bit of Erlang, Haskell, Racket, Standard ML, C#. Somewhere back in time Delphi, C++, assemblies. Lover of matrices, big data, statictics, machine learning and neural networks.

Updated on July 08, 2022

Comments

  • dmitry
    dmitry almost 2 years

    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?

  • dmitry
    dmitry over 11 years
    My god... I liked it so much and now I'm forced to place idiotic import language.postfixOps or use dots everywhere. Why this???!!!
  • Kim Stebel
    Kim Stebel over 11 years
    As my second example shows, it is not limited to nullary methods.
  • dmitry
    dmitry over 11 years
    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.
  • Kim Stebel
    Kim Stebel over 11 years
    @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.
  • dmitry
    dmitry over 11 years
    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
  • Kim Stebel
    Kim Stebel over 11 years
    Yes, I am sure. Just try it. RC1 only gives you a warning though.
  • Régis Jean-Gilles
    Régis Jean-Gilles over 11 years
    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".
  • dmitry
    dmitry over 11 years
    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.
  • Kim Stebel
    Kim Stebel over 11 years
    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.
  • dmitry
    dmitry over 11 years
    In your example there is no postfix operations. It is just ambiguous semantics in infix notations. Or where do I miss the thing?
  • Kim Stebel
    Kim Stebel over 11 years
    @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.
  • dmitry
    dmitry over 11 years
    Anyway, example is not about postfix. It is about dot which has higher precedence than method notation without dot.
  • Debilski
    Debilski over 11 years
    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.
  • Kim Stebel
    Kim Stebel over 11 years
    @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.
  • dmitry
    dmitry over 11 years
    @kim-stebel I've done. No Warning. Just Error: <console>: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.
  • Régis Jean-Gilles
    Régis Jean-Gilles over 11 years
    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 _).
  • dmitry
    dmitry over 11 years
    @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
    dmitry over 11 years
    @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
    dmitry over 11 years
    @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.
  • som-snytt
    som-snytt almost 11 years
    +1 for the interesting example in appender. That should be the scalapuzzler.
  • som-snytt
    som-snytt almost 11 years
    +1 for a comment section that's more engaging [to me] than the answer.