Viva la Scala

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 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 are AnyVal (value types, such as Int and Boolean) and AnyRef (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 just foo; method thread.send(signo) can also be called as just thread send signo; and method foo.toString() can also be called as just foo 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 Ints 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 and update have syntactic short forms. foo() — where foo is a value (singleton object or class instance) — is short for foo.apply(), and foo() = 42 is short for foo.update(42). Similarly, foo(42) is short for foo.apply(42), and foo(4) = 2 is short for foo.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 as Vector.fill(4)(math.random). The curly braces variant allows the expression to span multiple lines. breakable { ... if (...) break() ... } looks as if breakable 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, the 4 :: 2 :: Nil is the same as Nil.::(2).::(4), the first form corresponding visually to the result (a list with first element 4 and second element 2).

No Comments Yet