# Extension Methods, Opaque Types and Union Types — Scala

Source: https://www.geekswithgeeks.com/en/scala/t-modern

> Use Scala 3 features for safer, clearer APIs.

## Modern Scala 3 type features

**Extension methods** add methods to existing types without wrappers: `extension (s: String) def isPinCode: Boolean = ...`, after which `"411001".isPinCode` works wherever the extension is in scope. **Opaque type aliases** create zero-cost distinct types: `opaque type Paise = Long` is a plain `Long` at run time, but outside its defining scope the compiler treats it as a separate type, so you cannot accidentally pass a `Paise` where a `CustomerId` is expected. Provide constructors and operations in the companion object. **Union types** `A | B` express that a value is one of several types without a common hierarchy (`def parse(s: String): Int | ParseError`), and **intersection types** `A & B` require both. **Literal types** (`val mode: "test" | "live"`) restrict values precisely. **Match types** compute types from other types, and **inline** and the new macro system enable compile-time metaprogramming in libraries. Also useful: **`export`** clauses to forward members, and **top-level definitions**, so you no longer need package objects.

## Opaque types and extension methods

Distinct money and ID types at zero runtime cost.

```scala
object Domain:
  opaque type Paise = Long
  object Paise:
    def apply(value: Long): Paise =
      require(value >= 0, "money cannot be negative")
      value
    def fromRupees(rupees: BigDecimal): Paise = (rupees * 100).toLongExact

  extension (p: Paise)
    def +(other: Paise): Paise = p + other         // inside Domain, Paise is a Long
    def rupees: BigDecimal = BigDecimal(p) / 100

  opaque type CustomerId = String
  object CustomerId:
    def apply(raw: String): CustomerId = raw

import Domain.*

def credit(customer: CustomerId, amount: Paise): String = s"credited ${amount.rupees} to $customer"

val total = Paise(4950) + Paise.fromRupees(BigDecimal("199.00"))
println(credit(CustomerId("c-42"), total))       // credited 248.5 to c-42
// credit(total, CustomerId("c-42"))              // compile error: arguments swapped
// val wrong: Paise = 100L                        // compile error outside Domain

extension (s: String)
  def isPinCode: Boolean = s.length == 6 && s.forall(_.isDigit)

def parseQty(s: String): Int | String =           // union type: a number or an error message
  s.toIntOption.getOrElse(s"not a number: $s")

println("411001".isPinCode)                        // true
```

## Opaque types prevent mix-ups

Bugs like passing an order ID where a customer ID is expected, or rupees where paise are expected, become compile errors with opaque types, and they cost nothing at run time because they erase to the underlying type.

**Quiz:** What is the runtime cost of an opaque type alias such as opaque type Paise = Long?

- [ ] A wrapper object per value
- [ ] A reflection lookup
- [x] None: at run time it is the underlying Long
- [ ] A boxed Java object always

*Answer:* None: at run time it is the underlying Long. Opaque types are distinct only at compile time and erase to the underlying type.
