Skip to main content
edited body
Source Link

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.

Source Link

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:

  1. The . for method calls can be replaced by whitespace.
  2. When using the method calling syntax from #1, parentheses for a single argument list with a single argument are optional.
  3. 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 foo in scope, then foo(bar) translates to foo.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 an apply method, which allows you to construct e.g. a List with List(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 from Function).
  • Update syntax: foo(bar) = baz translates to foo.update(bar, baz). Not used extensively, as the Scala community eschews mutability.
  • Property syntax: If there is no mutable field (var) named bar, then foo.bar = baz translates to foo.bar_=(baz).
  • Assignment operators: +=, -=, etc. are just normal method names, so they can be implemented, i.e. a += b translates to a.+=(b). However, if there is no method named += but the expression a = a + b type checks, then that translation is chosen. This means += can be overloaded, but var n = 0; n += 1 still works as expected even though Int doesn't define +=.

There are some other "magic methods", but they are not related to operators, specifically:

  • for comprehensions de-sugar into calls to foreach, map, flatMap, and withFilter.
  • Pattern Matching de-sugars into calls to unapply and unapplySeq.

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` baz and foo `+` baz are always allowed and are equivalent to foo.bar(baz) and foo.+(baz).
  • It is still always possible to call a method whose name consists exclusively of operator characters using infix notation, i.e. a + b is still allowed.
  • Methods whose name contains alphanumeric characters can only be called using infix notation if they are explicitly marked infix at the definition site: infix def foo(bar: SomeParameterType): SomeResultType. Operator methods don't need an infix modifier.
  • For operator methods, it is encouraged to use the @targetName annotation 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): SomeResultType will be compiled into a Java method named prepend whereas 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