
- 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 - Dropped Features
Scala 3 introduced various changes and removed some features from Scala 2. These are various changes done in Scala 3 from Scala 2 as given below.
1. DelayedInit
DelayedInit is no longer supported. This trait was used to delay the initialization of classes and objects. So, you can run some code before the class and object initialization code. You should avoid using DelayedInit.
Scala 2:
trait Helper extends DelayedInit { def delayedInit(body: => Unit): Unit = { println("dummy text, printed before initialization of C") body // evaluates the initialization code of C } } class C extends Helper { println("this is the initialization code of C") } object Test extends App { val c = new C }
Here, Helper prints a message before running the initialization code of C using the delayedInit method. But you can initialize directly in the class in Scala 3 as given below.
Scala 3:
class C { println("this is the initialization code of C") } object Test extends App { val c = new C }
2. Scala 2 Macros
Scala 2 macro system has been replaced with a cleaner system based on inline and quote-splice mechanisms ('{...} and ${...}). So you can have better integration and safety.
Scala 2:
import scala.language.experimental.macros import scala.reflect.macros.blackbox.Context object Macros { def hello: Unit = macro helloImpl def helloImpl(c: Context): c.Expr[Unit] = { import c.universe._ reify { println("Hello, world!") } } } object Test extends App { Macros.hello }
Macros are used to generate code at compile time in Scala 2. The hello macro prints "Hello, world!" using a macro definition. The macro system is replaced with inline and quote-splice mechanisms in Scala 3 as given below.
Scala 3:
inline def hello: Unit = println("Hello, world!") object Test extends App { hello }
3. Existential Types
Existential types using forSome have been removed. Types that can be used with wildcards are still supported but are treated as refined types.
Scala 2:
def process(m: Map[_, _]): Unit = { println(m) } val m = Map("key" -> 42) process(m)
In Scala 2, existential types are used without knowing exactly what it is. Here, Map[_, _] is a map with any key and value types. Now, you can use wildcards in Scala, like Map[? <: Any, ? <: Any] for above same result -
Scala 3:
def process(m: Map[? <: Any, ? <: Any]): Unit = { println(m) } val m = Map("key" -> 42) process(m)
4. General Type Projection
General type projections like T#A where T is an abstract type is no longer supported. So you can encourage the use of path-dependent types and implicit parameters instead.
Scala 2:
trait T { type A } def foo(a: T#A): Unit = {}
You can define a type A inside a trait T and refer to it using T#A in Scala 2. This is known as type projection and used for type safety.
Scala 3:
trait T { type A def getA: A } def foo(t: T)(a: t.A): Unit = {}
You should use path-dependent types in Scala 3. Here, foo takes an instance of T and uses its type A. So it is safer and avoids the issues with type projections.
5. Do-While
The do-while loop syntax has been removed. You can use equivalent while loop instead for cleaner and more consistent syntax.
Scala 2:
var i = 0 def f(i: Int): Int = if (i < 5) 0 else 1 do { i += 1 } while (f(i) == 0) println(i) // Output: 5
In Scala 2, do-while loops were used to run a block of code at least once before checking the condition.
Scala 3:
var i = 0 def f(i: Int): Int = if (i < 5) 0 else 1 while { i += 1 f(i) == 0 } do () println(i) // Output: 5
In Scala 3, you should use a while loop with a block to achieve the same effect.
6. Procedure Syntax
Procedure syntax (def f() { ... }) has been dropped. Now you should use def f() = { ... } or def f(): Unit = { ... }.
Scala 2:
def f() { println("Hello") } f() // Output: Hello
Procedure syntax is used to define methods without specifying a return type in Scala 2. But, you need to specify the return type explicitly in Scala 3.
Scala 3:
def f() = { println("Hello") } f() // Output: Hello
7. Package Objects
Package objects are deprecated and will be removed. You can now write all kinds of definitions at the top level.
Scala 2:
package object p { val a = 42 def b = "Hello" } object Test extends App { println(p.a) // Output: 42 println(p.b) // Output: Hello }
Scala 3:
package p val a = 42 def b = "Hello" object Test extends App { println(a) // Output: 42 println(b) // Output: Hello }
8. Early Initializers
Early initializers (class C extends { ... } with SuperClass ...) have been removed. These were not used and are not necessary due to the support of trait parameters in Scala 3.
Scala 2:
class C extends { val x = 2 } with SuperClass
You were initializing parts of a class before calling the superclass constructor in Scala. But, you can use trait parameters to achieve the same effect in Scala 3. It avoids the need for early initializers in Scala 3.
Scala 3:
trait Base(val x: Int) class C extends Base(2) object Test extends App { val c = new C println(c.x) // Output: 2 }
9. Class Shadowing
Class shadowing is no longer supported to avoid confusion. Inner classes in subclasses can have the same name as in superclasses. This is known as class shadowing.
Scala 2:
class Base { class Ops } class Sub extends Base { class Ops }
You can have inner classes in subclasses with the same name as in superclasses in Scala 2. But it is not supported in Scala 3 to avoid confusion.
Scala 3:
class Base { class Ops } class Sub extends Base { class NewOps }
10. Limit 22
The limit of 22 parameters for functions and tuples is removed. Functions and tuples can now have any number of parameters and fields.
Scala 2:
val tuple = (1, 2, 3, ..., 22)
Tuples and functions were limited only to 22 parameters.
Scala 3:
val tuple = (1, 2, 3, ..., 50)
But, there is no limit on the number of parameters for tuples and functions in Scala 3.
11. XML Literals
XML literals are deprecated and will be replaced with XML string interpolation. This change improves the consistency and safety of XML handling in Scala.
Scala 2:
val xml = <message><from>John</from> <to>Doe</to></message> println(xml)
XML literals were used directly in the code in Scala 2.
Scala 3:
import dotty.xml.interpolator.* val xml = xml"<message><from>John</from> <to>Doe</to></message>" println(xml)
But, XML string interpolation is used instead of XML literals in Scala 3.
12. Symbol Literals
Symbol literals ('xyz) are no longer supported. You can use plain string literals ("xyz") instead. The Symbol class will be deprecated in the future.
Scala 2:
val sym = 'symbol println(sym) // Output: 'symbol
Symbol literals were used to create symbols in Scala 2.
Scala 3:
val sym = "symbol" println(sym) // Output: symbol
You need to use plain string literals instead of symbol literals in Scala 3.
13. Auto-Application
Implicit insertion of () for nullary methods is no longer supported. Now, it must match the parameter syntax exactly in Scala 3.
Scala 2:
def next(): Int = 42 val n = next println(n) // Output: 42
You can call a method without () even if it was defined with () in Scala 2.
Scala 3:
def next(): Int = 42 val n = next() println(n) // Output: 42
You need to match the method definition exactly in Scala 3. If the method is defined with (), you must call it with ().
14. Weak Conformance
The concept of weak conformance is used to widen numeric types in certain expressions. Now it has been removed.
Scala 2:
val list = List(1.0, math.sqrt(3.0), 0, -3.3) // List[Double] println(list)
Weak conformance was mixing different numeric types in a list. So it resulted in a list of the widest type. But, Scala 3 drops the general notion of weak conformance, and instead keeps one rule: Int literals are adapted to other numeric types if necessary.
15. Nonlocal Returns
Nonlocal returns from nested anonymous functions are deprecated due to hidden performance costs and unintended behavior. You can use scala.util.boundary and boundary.break instead in Scala 3.
Scala 2:
def foo(xs: List[Int]): Int = { xs.foreach { x => if (x > 10) return x } 0 } val result = foo(List(1, 2, 3, 11, 5)) println(result) // Output: 11
You can return from a nested anonymous function in Scala 2. But this had hidden costs and issues.
Scala 3:
import scala.util.boundary, boundary.break def foo(xs: List[Int]): Int = boundary { xs.foreach { x => if (x > 10) break(x) } 0 } val result = foo(List(1, 2, 3, 11, 5)) println(result) // Output: 11
You can use boundary and break for nonlocal returns in Scala 3. This approach is clearer and avoids hidden costs.
16. private[this] and protected[this]
The private[this] and protected[this] access modifiers are deprecated. The compiler now infers access only via this for private members. So these modifiers are redundant in Scala 3.
Scala 2:
class C { private[this] val x = 42 def getX: Int = x } val c = new C println(c.getX) // Output: 42
private[this] restricted access to within the instance itself in Scala 2.
Scala 3:
class C { private val x = 42 // treated as private[this] def getX: Int = x } val c = new C println(c.getX) // Output: 42
The compiler infers private[this] automatically for private members accessed only via this in Scala 3. This simplifies the code.
17. Wildcard Initializer
The syntax var x: A = _ for uninitialized fields has been dropped. Instead, you can use import scala.compiletime.uninitialized and var x: A = uninitialized in Scala 3.
Scala 2:
var x: Int = _ println(x) // Output: 0 (default initialization)
The _ was used to indicate uninitialized fields, which defaulted to a value in Scala 2.
Scala 3:
import scala.compiletime.uninitialized var x: Int = uninitialized println(x) // Output: <uninitialized>
You can use uninitialized for uninitialized fields in Scala 3.
These are various changes that have been made in Scala 3. These changes improve type safety and provide code clarity and consistency in Scala 3.