
- Scala - Home
- Scala - Overview
- Scala - Features
- Scala - Environment Setup
- Scala - Build Tool (SBT)
- Scala - REPL
- Scala - Dot & Dotty
- Scala - Basic Syntax
- Scala - Hello World Program
- Scala - Identifiers
- Scala - Keywords
- Scala - Comments
- Scala - Code Blocks
- Scala - Semicolon
- Scala - Constructs
- Scala - Expressions
- Scala - Input and Output
- Scala - Optional Braces
- Scala - Underscore (_)
- Data Types and Variables
- Scala - Data Types
- Scala - Type Bounds
- Scala - Context Bound
- Scala - Variances
- Scala - Type Hierarchy
- Scala - Variables
- Scala - Variable Scopes
- Scala - Literals
- Scala - Numeric Types
- Scala - Boolean Types
- Scala - Char Type
- Scala - Unit Types
- Scala - Strings
- Scala - Arrays
- Scala - Null Type
- Scala - Nothing
- Scala - Any Type
- Scala - AnyRef Type
- Scala - Unified Types
- Scala - Dates and Times
- Scala - Ranges
- Scala - Multidimensional Arrays
- Scala - WrappedArray
- Scala - StringBuilder
- Scala - String Interpolation
- Scala - StringContext
- Scala - Type Casting
- Scala var vs val
- Scala Operators
- Scala - Operators
- Scala - Rules for Operators
- Scala - Arithmetic Operators
- Scala - Relational Operators
- Scala - Logical Operators
- Scala - Bitwise Operators
- Scala - Assignment Operators
- Scala - Operators Precedence
- Scala - Symbolic Operators
- Scala - Range Operator
- Scala - String Concatenation Operator
- Scala Conditional Statements
- Scala - IF ELSE
- Scala - IF-ELSE-IF-ELSE Statement
- Scala - Nested IF-ELSE Statement
- Scala Loop Statements
- Scala - Loop Statements
- Scala - while Loop
- Scala - do-while Loop
- Scala - Nested Loops
- Scala - for Loop
- Scala - break Statement
- Scala - yield Keyword
- Scala Classes & Objects
- Scala - Classes & Objects
- Scala - Constructors
- Scala - Auxiliary Constructor
- Scala - Primary Constructor
- Scala - This Keyword
- Scala - Nested Classes
- Scala - Getters and Setters
- Scala - Object Private Fields
- Scala - Singleton Object
- Scala - Companion Objects
- Scala - Creating Executable Programs
- Scala - Stateful Object
- Scala - Enumerations
- Scala - Polymorphism
- Scala - Access Modifiers
- Scala - Apply Method
- Scala - Update Methods
- Scala - UnapplySeq Method
- Scala - Inheritance
- Scala - Extending a Class
- Scala - Method Overloading
- Scala - Method Overriding
- Scala - Generic Classes
- Scala - Generic Functions
- Scala - Superclass Construction
- Scala Methods & Functions
- Scala - Functions
- Scala - Main Methods
- Scala - Functions Call-by-Name
- Scala - Functions with Named Arguments
- Scala - Function with Variable Arguments
- Scala - Recursion Functions
- Scala - Default Parameter Values
- Scala - Functions without Parameters
- Scala - Implicit Parameters
- Scala - Higher-Order Functions
- Scala - Nested Functions
- Scala - Extension Methods
- Scala - Anonymous Functions
- Partially Applied Functions
- Scala - Lazy Val
- Scala - Pure Function
- Scala - Currying Functions
- Scala - Control Abstractions
- Scala - Corecursion
- Scala - Unfold
- Scala - Tail Recursion
- Scala - Infinite Sequences
- Scala - Dynamic Invocation
- Scala - Lambda Expressions
- Scala Collections
- Scala - Collections
- Mutable and Immutable Collections
- Scala - Lists
- Scala - Sets
- Scala - Maps
- Scala - TreeMap
- Scala - SortedMap
- Scala - Tuples
- Scala - Iterators
- Scala - Options
- Scala - Infinite Streams
- Scala - Parallel Collections
- Scala - Algebraic Data Types
- Scala Pattern Matching
- Scala - Pattern Matching
- Scala - Type Patterns
- Scala - Exception Handling
- Scala - Extractors
- Scala - Regular Expressions
- Scala Files I/O
- Scala - Files I/O
- Scala Advanced Concepts
- Scala - Closures
- Scala - Futures
- Scala - Promises
- Scala - Traits
- Scala - Trait Mixins
- Scala - Layered Traits
- Scala - Trait Linearization
- Scala - Sealed Traits
- Scala - Transparent Traits
- Scala - Literal Type Arithmetic
- Scala - Inline keyword
- Scala - Def, Var & Val
- Scala - Dropped Features
- Scala - BDD Testing
Scala - DOT and Dotty
Scala 3 uses Dotty which is a new compiler for Scala. Dotty has many language features and improvements.
Introduction of DOT and Dotty
The goal of the Scala programming language is to combine three elements:
- Types
- Functions
- Objects
Other languages focused on a part of these elements. For example, Lisp (functions and objects). Java (types and objects) Haskell and ML focused on functions and types. However, Scala stands out as the first language which brings together all three elements.
Scala 3 moves forward in unlocking the combination of object-oriented programming (OOP) and functional programming (FP) at the typed level. Fusion of OOP and FP is built on the foundation of DOT Calculus (DOT = OOP + FP).
DOT stands for Dependent Object Types which is the foundation of Scala 3. The concept behind DOT is core of Scala and advances with precise and robust foundations. Dotty serves as the compiler for building Scala 3 using the DOT Calculus. DOT is type-calculus used to prove that Dotty's language rules and its types are correct. DOT formally checks if the language rules are correct. Compiler research team is working on this to really understand the core and solid basis of Scala 3's type system.
DOT programming language has a type system. It is a small language but it is expressive. DOT is a simpler version of Scala. It can represent Scala features. All Scala code simplifies to DOT calculus. Programming languages Scala 2 and 3 are almost the same except for a few differences. The difference is that the Scala 3 compiler uses Dotty. Binary names of Scala 2 file and Scala 3 file are as `scalac` and `dotc` respectively.
Scala 3 has removed some useless features so it is smaller than Scala 2 language. However, Scala 3 has included new constructs for more usability, simplicity and consistency. Note that Scala 2 codes also work on Scala 3.
Scala Type System
Scala 3 has a simple and consistent type system. Here, we have discussed some changes in its type system.
1. Existential Types
From Scala 3, existential types and type projections have been removed. These were unnecessary so removed for simpler type systems and other constructs. However, projection on concrete type is supported on Scala.
2. Intersection and Union Types
Scala 3 has replaced compound types from Intersection types and Union Types. So, the subtype hierarchy of Scala has become a lattice. So, the compiler can find least lower bounds and upper bounds easily. It is easy for compilers to infer data types.
For example,
def fetchData(url: String): Data | String = try // logic to fetch and process data from the given URL processData(url) catch case _ => "Error fetching data"
Unions are duals of Intersection types. Union type (A|B) has all values of type A and Type B. Intersection types have all those members and properties that are common in both the types (A and B). It has the property of commutative. It means that A|B is same as B|A, i.e., both are equal. To decide if A|B is A or B, you can use pattern matching for this verification.
Example of intersection types:
trait Log: def logMessage: String trait Auditable: def auditAction: String type LoggableResource = Log & Auditable def performAudit(loggable: LoggableResource) = println(s"Auditing action: ${loggable.auditAction}") println(s"Logging message: ${loggable.logMessage}") object myResource extends Log, Auditable: override def logMessage = "Log entry for resource" override def auditAction = "Audit resource action" performAudit(myResource)
To represent common values of type A and type B, you use A&B. It has all members and properties of type A and type B. This property is also commutative, i.e., A&B is the same as B&A. But compound type is not commutative.
3. Type Lambda
Type Lambda comes with a cool syntax: [X] =>> F[X]. It is a higher-kinded type using type parameter X which creates type T often referring to X.
These are functions that go from one type to another:
trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } trait OptionInstance extends Functor[Option] { override def map[A, B](fa: Option[A])(f: A => B): Option[B] = ??? }
The old way of emulation of type lambda in Scala 2 was bad. It used a general type projection operator #, which is now removed.
trait OptionInstance extends Functor[({ type T[X] = Option[X] })#T] { override def pure[A](x: A) = ??? override def flatMap[A, B](fa: Option[A])(f: A => Option[B]) = ??? }
Traits
Until now in Scala 2, we did not pass parameters to a trait. To resolve this, a workaround was to turn the trait into an abstract class and provide its parameters in the subclass. This solution is fine unless we face issues with early initialization. The issue is that the abstract class parameters initialize after the subclass constructor evaluates. In Scala 3, no such problems arise because we can give parameters to traits.
For example, in Scala 3:
trait Animal(val sound: String) class Cat extends Animal("Meow") class Dog extends Animal("Woof")
Like classes, Traits can have parameters in Scala 3. These get evaluated before the trait initializes.
Enums
Writing enumerations was awkward in Scala before version 3. Also, creating basic ADT involved a lot of unnecessary repetitive code. So, it lacked expressiveness. Scala 3 introduces the 'enum' keyword for writing enumerations and ADTs easily. This one feature supports both enumeration and ADTs.
Using 'enum,' we can create a type with a set of named values. Let’s create a data type named "Color" with a fixed set of values:
enum Fruit: case Apple, Banana, Orange
To align our enum with Java enums, we extend java.lang.Enum.
enum Fruit extends java.util.Enum[Fruit]: case Apple, Banana, Orange
They can have parameters:
enum Fruit(val color: String): case Apple extends Fruit("Red") case Banana extends Fruit("Yellow") case Orange extends Fruit("Orange")
With enums, you can also write very expressive ADTs.
enum Result[+T, +E]: case Success(value: T) case Failure(error: E)
Implicits
Scala 2 implicits had many confusing aspects. So, it causes a lot of head-scratching. These are hard to understand, error-prone, misused and overused. There can be some confusion due to its complexity. But some issues are avoidable and just plain annoying.
In Scala 2, implicits serve various purposes. Key uses include:
- Offering Contextual Environment
- Creating Type Class Instances
- Implementing Extension Methods
- Applying Implicit Conversions
In Scala 2, implicits focus more on the mechanism than the intent. Scala 3 focuses on intent, introducing new keywords like given and using. Scala 2 implicits will stay available for a while due to compatibility concerns.
1. Providing Contextual Environment
The context includes outside information/parameters implicitly understood by our program. Scala 2 uses implicit parameters to pass contextual information into programs. These are basic ways to abstract over context.
For example, if a program needs ExecutionContext, you can create implicit parameter for it:
import scala.concurrent._ import scala.concurrent.duration._ implicit val customExecutionContext: scala.concurrent.ExecutionContext = ExecutionContext.global def cube(i: Int)(implicit val ec: ExecutionContext): Future[Int] = Future(i * i * i)
In Scala 3, you can pass a contextual environment by passing implicitly. The given keywords provide an instance of that contextual type. For example,
import scala.concurrent.ExecutionContext import scala.concurrent.Await import scala.concurrent.duration._ given ExecutionContext = ExecutionContext.global import scala.concurrent.Future def cube(i: Int)(using ec: ExecutionContext): Future[Int] = { Future(i * i * i) }
2. Writing Type Class Instances
In Scala 2, a key use of implicits is creating instances for type classes. For example,
trait Comparator[T] { def compare(x: T, y: T): Int } implicit stringComparator: Comparator[String] = new Comparator[String] { override def compare(x: String, y: String): Int = x.compareTo(y) }
Scala 3 introduces a special keyword for writing a type class instance.
trait Comparator[T]: def compare(x: T, y: T): Int given Comparator[String] with override def compare(x: String, y: String): Int = x.compareTo(y)
3. Extension Methods
Creating extension methods in Scala 2 contains some repetitive code. To extend type in Scala 2, we create a wrapper class. Then use implicit function to wrap type into extended class.
For example,
import scala.language.implicitConversions class EnhancedDouble(d: Double) { def cube: Double = d * d * d } object EnhancedDouble { implicit def enhanceDouble(d: Double): EnhancedDouble = new EnhancedDouble(d) }
Scala 3 introduces 'extension' keywords for writing extension methods. It is simple and concise syntax. For example,
extension (d: Double) def cube: Double = d * d * d
4. Implicit Conversions
In Scala 2, implicit conversions could cause unexpected bugs and require extra caution. In Scala 2, here is how to write an implicit conversion turning a String into Int:
import scala.language.implicitConversions implicit def stringToDouble(str: String): Double = str.toDouble def cube(d: Double): Double = d * d * d
In Scala 3, for implicit conversion from A to B, we need to provide a Conversion[A, B] instance.
import scala.language.implicitConversions given Conversion[String, Double] = _.toDouble def cube(d: Double): Double = d * d * d
Misusing implicit conversions in Scala 3 is hard. They eliminate unexpected behaviors. This method has fewer errors compared to Scala 2.