NashTech Blog

Lean Scala With Scala 3

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: 

  1.     scala.Any 
  2.     scala.AnyVal 
  3.     scala.Matchable 
  4.     scala.Product 
  5.     java.lang.Object 
  6.     java.lang.Comparable 
  7.     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: 

@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:

  1. https://docs.scala-lang.org/scala3/reference/index.html
  2. https://docs.scala-lang.org/scala3/book/introduction.html
  3. https://odersky.github.io/blog/2024-04-11-post.html

 

 

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top