What is Lean Scala?
- In Scala author ‘Martin Odersky’s words, Lean code is simple and understandable. It is as concise as possible without losing clarity. It avoids lingo, over-abstraction, and obscure features. It does not mislead, that is, it expresses the meaning of a program clearly and without fault.
- Below are some features provided by Scala 3 which support lean code writing.
1. Optional ‘new’ keyword:
In Scala 3, ‘new’ keyword is optional for all concrete classes. Previously, this feature was only limited to case classes. Check out the below example.
- Scala 2.x
class MyClass(val myField: Int) val myClassObj = new MyClass(1)
- Scala 3
class MyClass(val myField: Int) val myClassObj = MyClass(1)
2. Simplified and enhanced enums:
A new data type ‘enum’ has been introduced in Scala 3. These Enumerations can also be parameterized and can have members (fields & methods). However, writing Enums in Scala 2.x was a little complex.
- Scala 2.x
object WeekDay extends Enumeration { type WeekDay = Value val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value }
- Scala 3
enum WeekDay: case Mon, Tue, Wed, Thu, Fri, Sat, Sun enum Planet(mass: Double, radius: Double): private final val G = 6.67300E-11 def surfaceGravity = G * mass / (radius * radius) def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity case Mercury extends Planet(3.303e+23, 2.4397e6) case Earth extends Planet(5.976e+24, 6.37814e6)
3. Traits with parameters:
Scala 3 has added a a new feature where you can pass params to traits not supported earlier. So we needed to create an abstract class as a workaround.
- Scala 2.x
abstract class Greeting(val name: String) {def msg: String = s"How are you, $name"} class B extends Greeting("Bob") { println(msg) }
- Scala 3
trait Greeting(val name: String): def msg = s"How are you, $name" class B extends Greeting("Bob"): println(msg)
Check the link for restrictions to use this feature.
4. Embrace the immutability:
This is not about features of Scala 3 but this is a recommended way of writing Scala code.
By default, try to use immutable fields, only when there is an absolute necessity, use mutable fields.
E.g. Use,
val x = if(myBoolean) expr1 else expr
Instead of,
var x: ExprType = null if(myBoolean) x = expr1 else x = expr
5. Transparent Traits
If the trait is used as a mixin for other classes and traits, we generally do not want to see them in inferred types. An example is scala’s base Product trait that the compiler adds as a mixin trait to every case class or case object. In Scala 2, this parent trait sometimes makes inferred types more complicated than they should be.
- Scala 2.x
trait Kind case object Var extends Kind case object Val extends Kind val x = Set(if condition then Val else Var)
Here, the inferred type of x is Set[Kind & Product & Serializable] whereas one would have hoped it to be Set[Kind].
Scala 3 allows one to mark a trait or class as transparent, which can be suppressed in type inference.
- Scala 3
transparent trait Kind object Var extends Kind object Val extends Kind val x = Set(if true then Val else Var)
The following classes and traits are automatically treated as transparent:
- scala.Any
- scala.AnyVal
- scala.Matchable
- scala.Product
- java.lang.Object
- java.lang.Comparable
- java.io.Serializable
This means that an expression such as,
if condition then 1 else "hello
will have union type Int | String instead of the widened type Any. Check union types here.
6. Parameter Untupling
Say you have a list of pair val xs: List[(String, String)] and you want to map xs to a list of String so that each pair of strings is mapped to their concatenation. Previously, the best way to do this was with a pattern-matching decomposition:
- Scala 2.x
xs map { case (x, y) => x + y }
While correct, this is also inconvenient and confusing, since the case suggests that the pattern match could fail. As a shorter and clearer alternative Scala 3 now allows,
- Scala 3
xs.map { (x, y) => s”$x$y” }
7. Match expression chaining
You can chain multiple match expressions.
- Scala 3
xs match { case Nil => "empty" case _ => "nonempty" } match { case "empty" => 0 case "nonempty" => 1 }
8. Main method:
A @main annotation has been introduced in Scala 3. It can have an arbitrary number of parameters They replace the previous scheme to write programs as objects with a special App parent class.
- Scala 2.x
object hello extends App: // needs by-hand parsing of arguments vector ...
- Scala 3
@main def hello (name: String, others: String*) = println(s"Hello $name")
Conclusion:
The above list is not exhaustive, I will be writing the next article about more such features that are useful to write Lean Code. Happy coding!
References:
- https://docs.scala-lang.org/scala3/reference/index.html
- https://docs.scala-lang.org/scala3/book/introduction.html
- https://odersky.github.io/blog/2024-04-11-post.html