Scala allows calling arbitrary methods with one argument using infix systemsyntax and allows arbitrary operator characters in method names, therefore allowing arbitrary operators to be defined. Associativity and precedence, however, cannot be freely defined, they are based on the method name.
Scala allows calling arbitrary methods with one argument using infix system and allows arbitrary operator characters in method names, therefore allowing arbitrary operators to be defined. Associativity and precedence, however, cannot be freely defined, they are based on the method name.
Scala allows calling arbitrary methods with one argument using infix syntax and allows arbitrary operator characters in method names, therefore allowing arbitrary operators to be defined. Associativity and precedence, however, cannot be freely defined, they are based on the method name.
tl;dr
This is legal, runnable Scala code:
import language.postfixOps object Foo: def +(other: this.type) = "binary infix" def + = "unary postfix" def unary_+ = "unary prefix" Foo + Foo //=> "binary infix" Foo + //=> "unary postfix" + Foo //=> "unary prefix" Scala allows calling arbitrary methods with one argument using infix system and allows arbitrary operator characters in method names, therefore allowing arbitrary operators to be defined. Associativity and precedence, however, cannot be freely defined, they are based on the method name.
Scala 2 (Legacy)
Scala mostly doesn't deal with operators at all. There are three rules for method calls which allow Scala to support what "feels like" operators and operator overloading without actually having to deal with special support for operators and operator overloading:
- The
.for method calls can be replaced by whitespace. - When using the method calling syntax from #1, parentheses for a single argument list with a single argument are optional.
- A method whose name ends with
:is right-associative.
So, rule #1 means that I can replace
foo.bar(baz) with
foo bar(baz) and rule #2 means I can replace that with
foo bar baz There is nothing special about bar. Any method call expression with a single argument list with a single argument can be written this way.
Consequently, there is nothing special about
a + b It is simply the same as
a.+(b) applying rules #1 and #2 from above. + is just a legal name for a method like any other. Scala's rules for identifiers are roughly the same as for other popular languages (alphanumeric, can't start with a number, etc.), except that identifiers can also contain (and be completely composed of) operator characters. When mixing operators and alphanumerics, they need to be separated by an underscore. So, foo is legal ++--::::--++!!!**** is legal, foo_+ is legal.
The third rule means that
a +: b is actually equivalent to
b.+:(a) or, more precisely (observing left-to-right evaluation):
{ val __unspeakable_name__ = a; b.+:(__unspeakable_name__) } This is primarily used to support the standard :: operator for prepending an element to a CONS list. Naturally, in a single-dispatch OO language, that must be a method of the list object, but we still want to write the elements in the order they appear in the list:
val list = 1 :: 2 :: 3 :: Nil // is equivalent to val list = Nil.::(3).::(2).::(1) But what about precedence? And what about unary prefix operators? Okay, that's where my qualification from above comes in, where I wrote Scala "mostly" doesn't deal with operators.
For precedence, the rule is that the precedence is determined by the first character of the operator. I.e. all operators starting with + have the same precedence, all starting with - have the same precedence, and so on. The relative precedence between the starting characters is roughly the "standard" precedence from languages like C or Java, but with some modifications.
For unary prefix operators, Scala does not allow defining new ones. It only allows +, -, ~, and !. Unary prefix operator expressions are translated into method calls by prefixing unary_ to them, e.g.
+foo -foo ~foo !foo is equivalent to
foo.unary_+ foo.unary_- foo.unary_~ foo.unary_! Note how the rule that identifiers mixing alphanumeric and operator characters must be separated by an underscore is observed, so these are legal identifiers and can be defined using normal method definition syntax.
Unary postfix operators used to be allowed by simply observing rule #1 from above:
foo bar is equivalent to
foo.bar They are not technically deprecated (yet!) but their use is heavily discouraged. In current versions of Scala, they require an explicit import (or compiler flag), and will generate a compiler warning even if explicitly enabled.
There are a couple of other rules in order to support common use cases for operator overloading:
- Function application syntax: if there is no method named
fooin scope, thenfoo(bar)translates tofoo.apply(bar). This is used extensively for Factory methods, e.g. Scala has no literal syntax for collections, instead, all collections have companion objects with anapplymethod, which allows you to construct e.g. aListwithList(1, 2, 3, 4). This is also used for array and map indexing, treating arrays as functions of their indices and maps as functions of their keys (in fact, arrays and maps literally inherit fromFunction). - Update syntax:
foo(bar) = baztranslates tofoo.update(bar, baz). Not used extensively, as the Scala community eschews mutability. - Property syntax: If there is no mutable field (
var) namedbar, thenfoo.bar = baztranslates tofoo.bar_=(baz). - Assignment operators:
+=,-=, etc. are just normal method names, so they can be implemented, i.e.a += btranslates toa.+=(b). However, if there is no method named+=but the expressiona = a + btype checks, then that translation is chosen. This means+=can be overloaded, butvar n = 0; n += 1still works as expected even thoughIntdoesn't define+=.
There are some other "magic methods", but they are not related to operators, specifically:
forcomprehensions de-sugar into calls toforeach,map,flatMap, andwithFilter.- Pattern Matching de-sugars into calls to
unapplyandunapplySeq.
An interesting tidbit is that infix notation is also valid for type constructors. Application of a binary type constructor C[A, B] can be written as A C B. This is rarely used but can be useful in the context of type-level programming. For example, the type-constructor =:=[A, B] compiles IFF A and B are equal, and it should be obvious why being able to write it as A =:= B is desirable.
Scala 3 (Current)
Some of the rules above have been tightened up in Scala 3:
- It is always possible to call a method using infix syntax by enclosing it in backticks, i.e.
foo `bar` bazandfoo `+` bazare always allowed and are equivalent tofoo.bar(baz)andfoo.+(baz). - It is still always possible to call a method whose name consists exclusively of operator characters using infix notation, i.e.
a + bis still allowed. - Methods whose name contains alphanumeric characters can only be called using infix notation if they are explicitly marked
infixat the definition site:infix def foo(bar: SomeParameterType): SomeResultType. Operator methods don't need aninfixmodifier. - For operator methods, it is encouraged to use the
@targetNameannotation which allows you to choose a human-readable name for the method in the actual compiled code. This is especially useful if the name is illegal in the underlying target platform (e.g. the JVM). For example,@targetName("prepend") def ++:(other: SomeParameterType): SomeResultTypewill be compiled into a Java method namedprependwhereas without the annotation it would be something like$plus$plus$colon, which would be the name you need to use if you want to call this method from Java, Kotlin, Ruby, or any other JVM language.
But the fundamental idea of trying to, as much as possible, treat operators as methods like any other, just with funky names, remains.
Disclosure: this answer is copy&pasted from https://langdev.stackexchange.com/a/1759/854