scalaintermediate
Multiversal Equality in Scala 3
Use strict equality with CanEqual to prevent comparing unrelated types at compile time.
scalaPress ⌘/Ctrl + Shift + C to copy
import scala.language.strictEquality
// With strict equality, this won't compile:
// val x: Int = 1
// val s: String = "1"
// x == s // Error! Can't compare Int and String
// Opt-in equality
case class UserId(value: Long) derives CanEqual
case class OrderId(value: Long) derives CanEqual
// UserId(1) == OrderId(1) // Won't compile!
// UserId(1) == UserId(1) // OK
// Custom CanEqual for related types
sealed trait Currency derives CanEqual
case object USD extends Currency
case object EUR extends Currency
case object GBP extends Currency
case class Money(amount: Double, currency: Currency) derives CanEqual
// Allow comparing Money instances
given CanEqual[Money, Money] = CanEqual.derived
// Enum with equality
enum Color derives CanEqual:
case Red, Green, Blue
enum Size derives CanEqual:
case Small, Medium, Large
// Color.Red == Size.Small // Won't compile! Different types
// With inheritance
sealed trait Shape derives CanEqual
case class Circle(r: Double) extends Shape
case class Square(s: Double) extends Shape
// Allows Shape subtypes to be compared
given CanEqual[Shape, Shape] = CanEqual.derived
@main def run(): Unit =
// Same type comparison works
val id1 = UserId(1)
val id2 = UserId(1)
val id3 = UserId(2)
println(s"id1 == id2: ${id1 == id2}") // true
println(s"id1 == id3: ${id1 == id3}") // false
// Currency comparison
println(s"USD == USD: ${USD == USD}") // true
println(s"USD == EUR: ${USD == EUR}") // false
// Money comparison
val m1 = Money(100, USD)
val m2 = Money(100, USD)
val m3 = Money(100, EUR)
println(s"m1 == m2: ${m1 == m2}") // true
println(s"m1 == m3: ${m1 == m3}") // false
// Shape comparison
val shapes: List[Shape] = List(Circle(5), Square(3), Circle(5))
println(s"Same circle: ${shapes(0) == shapes(2)}")
println(s"Circle vs Square: ${shapes(0) == shapes(1)}")
// Pattern matching still works
val color: Color = Color.Red
color match
case Color.Red => println("Red!")
case Color.Green => println("Green!")
case Color.Blue => println("Blue!")Use Cases
- Preventing accidental type comparisons
- Domain type safety
- Compile-time equality checking
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
scalaadvanced
Opaque Types for Type Safety
Use Scala 3 opaque types to create zero-cost type wrappers for domain modeling.
Best for: Domain-driven type safety
#scala#opaque-types
scalaintermediate
Given Imports and Exports
Manage given instances with Scala 3 import syntax: selective imports, exports, and priorities.
Best for: Organizing type class instances
#scala#given
scalaintermediate
Union and Intersection Types
Use Scala 3 union and intersection types for flexible and precise type definitions.
Best for: Flexible function parameters
#scala#union-types
scalaadvanced
Phantom Types for Compile-Time Safety
Use phantom types to encode state and constraints at the type level without runtime cost.
Best for: State machine enforcement at compile time
#scala#phantom-types