Scala's "postfix ops"
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
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, 2022Comments
-
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 ispostfixOps
, to which I just cannot find a reference anywhere. What exactly does this feature allow? -
dmitry over 11 yearsMy 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 over 11 yearsAs my second example shows, it is not limited to nullary methods.
-
dmitry over 11 yearsActually 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 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 over 11 yearsAre 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) thanList(1,2,3).map( _ + 1).reverse
-
Kim Stebel over 11 yearsYes, I am sure. Just try it. RC1 only gives you a warning though.
-
Régis Jean-Gilles over 11 yearsI 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 over 11 yearsYes, 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 over 11 yearsNo, 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 over 11 yearsIn your example there is no postfix operations. It is just ambiguous semantics in infix notations. Or where do I miss the thing?
-
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 over 11 yearsAnyway, example is not about postfix. It is about dot which has higher precedence than method notation without dot.
-
Debilski over 11 yearsWould be nice (but even more confusing), if Scala had a lower precedence alternative for
.
. Then we might haveList(1,2,3) map (_ * 2) filter (_ != 2) $ reverse
and exchange a$
for dots or parentheses. -
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 over 11 years@kim-stebel I've done. No Warning. Just Error:
<console>:7: error: value ::: is not a member of Unit
. And just becauseList(3,4,5).foreach {println}
has been executed first and producedUnit
. No postfix involved. -
Régis Jean-Gilles over 11 yearsFair 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 asmyObject.myMethod _
). -
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 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 producesList(6, 4)
and without postfix ops it would be:(List(1,2,3) map (_ * 2) filter (_ != 2)).reverse
? Is it better? -
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 almost 11 years+1 for the interesting example in appender. That should be the scalapuzzler.
-
som-snytt almost 11 years+1 for a comment section that's more engaging [to me] than the answer.