Newtype
What is Newtype?
Newtype lets you create domain-specific types from existing value types with zero runtime overhead.
Newtype gives you:
- stronger type-safety at compile-time
- the same runtime representation as the original type
- clear domain boundaries without wrappers or allocations
Newtype does not perform validation.
If you need validation rules, use Refined or InlinedRefined.
Import
import refined4s.*
Define a Newtype
type NewtypeName = NewtypeName.Type
object NewtypeName extends Newtype[ActualType]
Example:
type Name = Name.Type
object Name extends Newtype[String]
Create Values
val newtypeName = NewtypeName(value)
val name = Name("Kevin")
// name: Type = "Kevin"
Get Actual Value
Use .value to unwrap the underlying value.
newtypeName.value
name.value
// res1: String = "Kevin"
Pattern Matching
Newtype provides unapply, so you can pattern match directly.
name match {
case Name(value) =>
println(s"Pattern matched value: $value")
}
// Pattern matched value: Kevin
Type-Safety Example
import refined4s.*
type Name = Name.Type
object Name extends Newtype[String]
type Email = Email.Type
object Email extends Newtype[String]
def hello(name: Name): Unit = println(s"Hello ${name.value}")
def send(email: Email): Unit = println(s"Sending email to ${email.value}")
val name = Name("Kevin")
// name: Type = "Kevin"
hello(name)
// Hello Kevin
val email = Email("kevin@blah.blah")
// email: Type = "kevin@blah.blah"
send(email)
// Sending email to kevin@blah.blah
hello("Kevin")
// error:
// Found: ("Kevin" : String)
// Required: repl.MdocSession.MdocApp1.Name
send("kevin@blah.blah")
// error:
// Found: ("kevin@blah.blah" : String)
// Required: repl.MdocSession.MdocApp1.Email
Functional Typeclass Derivation
Newtype can derive typeclass instances from its underlying type using deriving.
e.g.) Sorting the Name from the example above fails because Name does not have an Ordering instance.
List(Name("c"), Name("a"), Name("b")).sorted
// error:
// No given instance of type Ordering[repl.MdocSession.MdocApp1.Name.Type] was found for parameter ord of method sorted in trait StrictOptimizedSeqOps.
// I found:
//
// scala.math.Ordering.comparatorToOrdering[repl.MdocSession.MdocApp1.Name.Type](
// /* missing */summon[java.util.Comparator[repl.MdocSession.MdocApp1.Name.Type]]
// )
//
// But no implicit values were found that match type java.util.Comparator[repl.MdocSession.MdocApp1.Name.Type].
// List(Name("c"), Name("a"), Name("b")).sorted
// ^^^^^^
You can easily derive an Ordering instance for Name using deriving. So Ordering[Name] is derived from Ordering[String].
given Ordering[Name] = Name.deriving[Ordering]
List(Name("c"), Name("a"), Name("b")).sorted
// res9: List[Type] = List("a", "b", "c")