Scala is a very scalable language. It actually is true to its portmanteau name. But scalability is a double-edged sword.
With great scalability, comes great extensibility. And confusion. Lots and lots of confusions.
We will have a look at a curated list of Scala's extensibility features. I have also noticed possible sources of confusions about Scala's syntax in my earlier post even though I was writing merely about the basics of Scala's type system.
Fortunately, Scala's syntax can be easily understood with an hour or less of on 'Tour of Scala' on the official site. An even shorter intro for people with Java, Kotlin or TypeScript experience can be found on just the [wikipedia page for Scala](en.wikipedia.org/wiki/Scala_(programming_la.. of all places.
For further convenience, we will put together an ensemble of simple explainers of Scala's unique principles, features and syntax here. These are mainly going to be from the above links and the further reading links listed in the previous article.
Speaking of staying true to its name, this post itself will be a living document that we shall together keep updated. I hope we can form a one stop shop for picking up essential notes on Scala concepts and syntax details to get started. Explainers on control structures, types and advanced features will also be welcome. However we might need to put those ones elsewhere in the MAP series or the Play with Spring series if they are not already covered.
Viva la Scala!
Unified Type System
As we discussed before in MAP 3: Types of Types, every type in Scala has the top type Any
as its supertype and the bottom type Nothing
as its subtype.
In Scala, all types inherit from a top-level class
Any
, whose immediate children areAnyVal
(value types, such asInt
andBoolean
) andAnyRef
(reference types, as in Java). This means that [...] in Scala, boxing and unboxing is completely transparent to the user. Scala 2.10+ allows for new value types to be defined by the user.
Uniform Access Principle
This principle originally proposed by the computer scientist Bertrand Meyer, creator of Eiffel language, states that
"All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation".
In Scala it essentially translates to stipulating that calling an attribute, pre-computed property, method of an object etc all look the same syntactically. The semantics may defer as needed. For instance, even though values are evaluated only once, methods are evaluated each time they are invoked.
Function or method
foo()
can also be called as justfoo
; methodthread.send(signo)
can also be called as justthread send signo
; and methodfoo.toString()
can also be called as justfoo toString
Dot Free and Point Free
As we saw in the above uniform access section, Scala supports a dot free notation for calling methods and members.
Any method can be used as an infix operator, e.g. "%d apples".format(num) and "%d apples" format num are equivalent.
'Dot free' notation is not to be confused with 'point free' notation or tacit programming style in which function definitions do not identify the arguments (or 'points' in topology terms). This style allows for concise syntax, focus on functions instead of data, easier function composition and currying, among other things. As of the time of writing, using point free style in Scala for anything but trivial pieces of code would not be advised.
//verbose
List("dot", "free", "vs", "point", "free").foreach(s => println(s))
//dot free
List("dot", "free", "vs", "point", "free") foreach {println(_)}
//point free version:
List("dot", "free", "vs", "point", "free") foreach println
It is not easy for Scala to infer types when you use this parameter free style due to its local type inference system. If you are familiar with Haskell or SML with an essentially global Hindley-Milner type system, these are the kinds of type system super powers you will miss in Scala.
Infix and Symbolic Operators
In the expression +1
, +
is a prefix and also a unary operator. But when adding two Int
s as in 1 + 2
, +
is an infix operator defined for Scala's integer type. +
is a bona-fide method name in Scala defined separately for all Scala types where addition makes sense. This includes BigInt
and BigDecimal
types too unlike in Java where arithmetic operations for Big* types cannot use the arithmetic operators +
, -
, /
, and *
.
As we saw in the dot free and point free example, methods can be treated as infix operators.
In fact, arithmetic operators like
+
and<<
are treated just like any other methods, since function names are allowed to consist of sequences of arbitrary symbols (with a few exceptions made for things like parens, brackets and braces that must be handled specially); the only special treatment that such symbol-named methods undergo concerns the handling of precedence.
There are no postfix operators like ++
or --
in Scala. The value types in Scala are immutable by default so you cannot just add these to them without breaking the type system. If you happen to need the mutable var
variables for a value type, you can just use +=
or -=
like counter += 1
. You can also use *=
or /=
.
Setters and Getters
This one is for allowing setters and getters.
Class body variables can be transparently implemented as separate getter and setter methods. For
trait FooLike { var bar: Int }
, an implementation may be
object Foo extends FooLike {
private var x = 0;
def bar = x;
def bar_=(value: Int) { x = value }} } }
The call site will still be able to use a concise foo.bar = 42.
Apply and Update
This one allows for simple creation of collections, case classes or complex objects without having to new
up a new instance, usually an empty one and then populate it next. It is also handy for defining default apply
and update
behaviors that make sense for your class.
Methods
apply
andupdate
have syntactic short forms.foo()
— wherefoo
is a value (singleton object or class instance) — is short forfoo.apply()
, andfoo() = 42
is short forfoo.update(42)
. Similarly,foo(42)
is short forfoo.apply(42)
, andfoo(4) = 2
is short forfoo.update(4, 2)
. This is used for collection classes and extends to many other cases, such as STM cells.
Curly Braces and Parentheses
The use of curly braces instead of [or after] parentheses is allowed in method calls. This allows pure library implementations of new control structures. [...]
Vector.fill(4) { math.random }
is the same asVector.fill(4)(math.random)
. The curly braces variant allows the expression to span multiple lines.breakable { ... if (...) break() ... }
looks as ifbreakable
was a language defined keyword, but really is just a method taking a thunk argument.
Colon-oscopy
This one is for inline list creation and for colon-suffixed methods to be invoked in the order that looks intuitive:
Method names ending in colon
:
expect the argument on the left-hand-side and the receiver on the right-hand-side. For example, the4 :: 2 :: Nil
is the same asNil.::(2).::(4)
, the first form corresponding visually to the result (a list with first element 4 and second element 2).