The document discusses different approaches to representing data with smart types in Scala to validate data at compile time. It describes using smart constructors with Either types, the Newtype library, and the Refined library to define types for a Person case class that validate fields like names, emails and addresses. Using these approaches helps prevent invalid data from being stored by catching errors at compile time rather than runtime.
4. Background
ZIO Prelude is a Scala-first take on functional abstractions
• Type classes to describe the ways different types are
similar
5. Background
ZIO Prelude is a Scala-first take on functional abstractions
• Type classes to describe the ways different types are
similar
• Data types that complement the Scala standard library
(NonEmptyList, ZValidation, ZPure)
6. Background
ZIO Prelude is a Scala-first take on functional abstractions
• Type classes to describe the ways different types are
similar
• Data types that complement the Scala standard library
(NonEmptyList, ZValidation, ZPure)
• Smart Types
17. Smart Constructors
final case class Name private (name: String)
object Name {
def make(value: String): Either[String, Name] =
if (value.nonEmpty) Right(Name(value))
else Left("Name cannot be empty")
}
18. Smart Constructors
final case class Name private (name: String)
object Name {
def make(value: String): Either[String, Name] =
if (value.nonEmpty) Right(Name(value))
else Left("Name cannot be empty")
}
19. Smart Constructors
final case class Name private (name: String)
object Name {
def make(value: String): Either[String, Name] =
if (value.nonEmpty) Right(Name(value))
else Left("Name cannot be empty")
}
20. Smart Constructors
final case class Name private (name: String)
object Name {
def make(value: String): Either[String, Name] =
if (value.nonEmpty) Right(Name(value))
else Left("Name cannot be empty")
}
21. Smart Constructors
final case class Email private (email: String)
object Email {
def make(value: String): Either[String, Email] = {
val regex = "^[w-.]+@([w-]+.)+[w-]{2,4}$"
if (value.matches(regex)) Right(Email(value))
else Left(s"$value does not match $regex")
}
}
22. Smart Constructors
final case class Address private (address: String)
object Address {
def make(value: String): Either[String, Address] =
if (value.nonEmpty) Right(Address(value))
else Left("Address cannot be empty")
}
final case class Person(name: Name, email: Email, address: Address)
23. Smart Constructors
final case class Address private (address: String)
object Address {
def make(value: String): Either[String, Address] =
if (value.nonEmpty) Right(Address(value))
else Left("Address cannot be empty")
}
final case class Person(name: Name, email: Email, address: Address)
24. Smart Constructors
final case class Address private (address: String)
object Address {
def make(value: String): Either[String, Address] =
if (value.nonEmpty) Right(Address(value))
else Left("Address cannot be empty")
}
final case class Person(name: Name, email: Email, address: Address)
25.
26.
27. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
} yield Person(name, email, address)
28. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
} yield Person(name, email, address)
29. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
} yield Person(name, email, address)
30. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
} yield Person(name, email, address)
31. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
} yield Person(name, email, address)
32. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
} yield Person(name, email, address)
33. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
} yield Person(name, email, address)
34. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
} yield Person(name, email, address)
35. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
} yield Person(name, email, address)
36. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
} yield Person(name, email, address)
37. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
} yield Person(name, email, address)
38. Smart Constructors
val person: Either[String, Person] =
for {
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
} yield Person(name, email, address)
39.
40. Using the Newtype library
import io.estatico.newtype.macros.newtype
import io.estatico.newtype.ops._
@newtype class Name(name: String)
object Name {
def make(value: String): Either[String, Name] =
if (value.nonEmpty) Right(value.coerce)
else Left("Name cannot be empty")
}
41. Using the Newtype library
import io.estatico.newtype.macros.newtype
import io.estatico.newtype.ops._
@newtype class Email(email: String)
object Email {
def make(value: String): Either[String, Email] = {
val regex = "^[w-.]+@([w-]+.)+[w-]{2,4}$"
if (value.matches(regex)) Right(value.coerce)
else Left(s"$value does not match $regex")
}
}
42. Using the Newtype library
import io.estatico.newtype.macros.newtype
import io.estatico.newtype.ops._
@newtype class Address(address: String)
object Address {
def make(value: String): Either[String, Address] =
if (value.nonEmpty) Right(value.coerce)
else Left("Address cannot be empty")
}
final case class Person(name: Name, email: Email, address: Address)
43. Using the Newtype library
import io.estatico.newtype.macros.newtype
import io.estatico.newtype.ops._
@newtype class Address(address: String)
object Address {
def make(value: String): Either[String, Address] =
if (value.nonEmpty) Right(value.coerce)
else Left("Address cannot be empty")
}
final case class Person(name: Name, email: Email, address: Address)
44. Using the Newtype library
import io.estatico.newtype.macros.newtype
import io.estatico.newtype.ops._
@newtype class Address(address: String)
object Address {
def make(value: String): Either[String, Address] =
if (value.nonEmpty) Right(value.coerce)
else Left("Address cannot be empty")
}
final case class Person(name: Name, email: Email, address: Address)
45.
46. Newtype library
val person: Either[String, Person] =
for {
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
} yield Person(name, email, address)
47. Newtype library
val person: Either[String, Person] =
for {
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
} yield Person(name, email, address)
48.
49. Using the Refined library
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection._
import eu.timepit.refined.string._
type Name = String Refined NonEmpty
type Email = String Refined MatchesRegex["^[w-.]+@([w-]+.)+[w-]{2,4}$"] // This works since Scala 2.13 thanks to literal types
type Email = String Refined MatchesRegex[W.`^[w-.]+@([w-]+.)+[w-]{2,4}$`.T] // Before Scala 2.13
type Address = String Refined NonEmpty
final case class Person(name: Name, email: Email, address: Address)
50. Using the Refined library
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection._
import eu.timepit.refined.string._
type Name = String Refined NonEmpty
type Email = String Refined MatchesRegex["^[w-.]+@([w-]+.)+[w-]{2,4}$"] // This works since Scala 2.13 thanks to literal types
type Email = String Refined MatchesRegex[W.`^[w-.]+@([w-]+.)+[w-]{2,4}$`.T] // Before Scala 2.13
type Address = String Refined NonEmpty
final case class Person(name: Name, email: Email, address: Address)
51. Using the Refined library
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection._
import eu.timepit.refined.string._
type Name = String Refined NonEmpty
type Email = String Refined MatchesRegex["^[w-.]+@([w-]+.)+[w-]{2,4}$"] // This works since Scala 2.13 thanks to literal types
type Email = String Refined MatchesRegex[W.`^[w-.]+@([w-]+.)+[w-]{2,4}$`.T] // Before Scala 2.13
type Address = String Refined NonEmpty
final case class Person(name: Name, email: Email, address: Address)
52. Using the Refined library
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection._
import eu.timepit.refined.string._
type Name = String Refined NonEmpty
type Email = String Refined MatchesRegex["^[w-.]+@([w-]+.)+[w-]{2,4}$"] // This works since Scala 2.13 thanks to literal types
type Email = String Refined MatchesRegex[W.`^[w-.]+@([w-]+.)+[w-]{2,4}$`.T] // Before Scala 2.13
type Address = String Refined NonEmpty
final case class Person(name: Name, email: Email, address: Address)
53. Using the Refined library
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection._
import eu.timepit.refined.string._
type Name = String Refined NonEmpty
type Email = String Refined MatchesRegex["^[w-.]+@([w-]+.)+[w-]{2,4}$"] // This works since Scala 2.13 thanks to literal types
type Email = String Refined MatchesRegex[W.`^[w-.]+@([w-]+.)+[w-]{2,4}$`.T] // Before Scala 2.13
type Address = String Refined NonEmpty
final case class Person(name: Name, email: Email, address: Address)
54. Using the Refined library
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection._
import eu.timepit.refined.string._
type Name = String Refined NonEmpty
type Email = String Refined MatchesRegex["^[w-.]+@([w-]+.)+[w-]{2,4}$"] // This works since Scala 2.13 thanks to literal types
type Email = String Refined MatchesRegex[W.`^[w-.]+@([w-]+.)+[w-]{2,4}$`.T] // Before Scala 2.13
type Address = String Refined NonEmpty
final case class Person(name: Name, email: Email, address: Address)
55. Compile-time
validations with
Refined
val name1: Name = "Jorge"
val email1: Email = "jorge.vasquez@scalac.io"
val address1: Address = "100 Some St."
val person1: Person = Person(name1, email1, address1)
56. Compile-time
validations with
Refined
val name1: Name = "Jorge"
val email1: Email = "jorge.vasquez@scalac.io"
val address1: Address = "100 Some St."
val person1: Person = Person(name1, email1, address1)
57. Compile-time
validations with
Refined
val name1: Name = "Jorge"
val email1: Email = "jorge.vasquez@scalac.io"
val address1: Address = "100 Some St."
val person1: Person = Person(name1, email1, address1)
58. Compile-time
validations with
Refined
val name1: Name = "Jorge"
val email1: Email = "jorge.vasquez@scalac.io"
val address1: Address = "100 Some St."
val person1: Person = Person(name1, email1, address1)
59. Compile-time
validations with
Refined
val name1: Name = "Jorge"
val email1: Email = "jorge.vasquez@scalac.io"
val address1: Address = "100 Some St."
val person1: Person = Person(name1, email1, address1)
60. Compile-time
validations with
Refined
val name2: Name = ""
// Predicate isEmpty() did not fail
val email2: Email = "whatever"
// Predicate failed:
// "whatever".matches("^[w-.]+@([w-]+.)+[w-]{2,4}$").
val address2: Address = ""
// Predicate isEmpty() did not fail
val person2: Person = Person(name2, email2, address2)
61. Compile-time
validations with
Refined
val name2: Name = ""
// Predicate isEmpty() did not fail
val email2: Email = "whatever"
// Predicate failed:
// "whatever".matches("^[w-.]+@([w-]+.)+[w-]{2,4}$").
val address2: Address = ""
// Predicate isEmpty() did not fail
val person2: Person = Person(name2, email2, address2)
62. Compile-time
validations with
Refined
val name2: Name = ""
// Predicate isEmpty() did not fail
val email2: Email = "whatever"
// Predicate failed:
// "whatever".matches("^[w-.]+@([w-]+.)+[w-]{2,4}$").
val address2: Address = ""
// Predicate isEmpty() did not fail
val person2: Person = Person(name2, email2, address2)
63. Compile-time
validations with
Refined
val name2: Name = ""
// Predicate isEmpty() did not fail
val email2: Email = "whatever"
// Predicate failed:
// "whatever".matches("^[w-.]+@([w-]+.)+[w-]{2,4}$").
val address2: Address = ""
// Predicate isEmpty() did not fail
val person2: Person = Person(name2, email2, address2)
64. Compile-time
validations with
Refined
val name2: Name = ""
// Predicate isEmpty() did not fail
val email2: Email = "whatever"
// Predicate failed:
// "whatever".matches("^[w-.]+@([w-]+.)+[w-]{2,4}$").
val address2: Address = ""
// Predicate isEmpty() did not fail
val person2: Person = Person(name2, email2, address2)
68. Runtime validations
with Refined
val name3: Either[String, Name] =
refineV(scala.io.StdIn.readLine())
val email3: Either[String, Email] =
refineV(scala.io.StdIn.readLine())
val address3: Either[String, Address] =
refineV(scala.io.StdIn.readLine())
val person3a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
69. Runtime validations
with Refined
val name3: Either[String, Name] =
refineV(scala.io.StdIn.readLine())
val email3: Either[String, Email] =
refineV(scala.io.StdIn.readLine())
val address3: Either[String, Address] =
refineV(scala.io.StdIn.readLine())
val person3a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
70. Runtime validations
with Refined
val name3: Either[String, Name] =
refineV(scala.io.StdIn.readLine())
val email3: Either[String, Email] =
refineV(scala.io.StdIn.readLine())
val address3: Either[String, Address] =
refineV(scala.io.StdIn.readLine())
val person3a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
71. Runtime validations
with Refined
val name3: Either[String, Name] =
refineV(scala.io.StdIn.readLine())
val email3: Either[String, Email] =
refineV(scala.io.StdIn.readLine())
val address3: Either[String, Address] =
refineV(scala.io.StdIn.readLine())
val person3a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
72. Runtime validations
with Refined
val name3: Either[String, Name] =
refineV(scala.io.StdIn.readLine())
val email3: Either[String, Email] =
refineV(scala.io.StdIn.readLine())
val address3: Either[String, Address] =
refineV(scala.io.StdIn.readLine())
val person3a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
73. Runtime validations
with Refined
val name3: Either[String, Name] =
refineV(scala.io.StdIn.readLine())
val email3: Either[String, Email] =
refineV(scala.io.StdIn.readLine())
val address3: Either[String, Address] =
refineV(scala.io.StdIn.readLine())
val person3b: Either[String, Person] =
for {
name <- address3
email <- email3
address <- name3
} yield Person(name, email, address)
74.
75. Refined with Smart Constructors!
final case class Name(name: String Refined NonEmpty)
object Name {
def make(value: String): Either[String, Name] = refineV[NonEmpty](value).map(Name(_))
}
76. Refined with Smart Constructors!
type EmailRegex = MatchesRegex["^[w-.]+@([w-]+.)+[w-]{2,4}$"]
final case class Email(email: String Refined EmailRegex)
object Email {
def make(value: String): Either[String, Email] =
refineV[EmailRegex](value).map(Email(_))
}
77. Refined with Smart Constructors!
final case class Address(address: String Refined NonEmpty)
object Address {
def make(value: String): Either[String, Address] = refineV[NonEmpty](value).map(Address(_))
}
final case class Person(name: Name, email: Email, address: Address)
78. Refined with Smart Constructors!
final case class Address(address: String Refined NonEmpty)
object Address {
def make(value: String): Either[String, Address] = refineV[NonEmpty](value).map(Address(_))
}
final case class Person(name: Name, email: Email, address: Address)
79. Refined with Smart
Constructors!
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
80. Refined with Smart
Constructors!
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
81. Refined with Smart
Constructors!
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
82. Refined with Smart
Constructors!
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
83. Refined with Smart
Constructors!
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
84. Refined with Smart
Constructors!
val name2: Either[String, Name] =
Name.make(scala.io.StdIn.readLine())
val email2: Either[String, Email] =
Email.make(scala.io.StdIn.readLine())
val address2: Either[String, Address] =
Address.make(scala.io.StdIn.readLine())
val person2a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
85. Refined with Smart
Constructors!
val name2: Either[String, Name] =
Name.make(scala.io.StdIn.readLine())
val email2: Either[String, Email] =
Email.make(scala.io.StdIn.readLine())
val address2: Either[String, Address] =
Address.make(scala.io.StdIn.readLine())
val person2a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
86. Refined with Smart
Constructors!
val name2: Either[String, Name] =
Name.make(scala.io.StdIn.readLine())
val email2: Either[String, Email] =
Email.make(scala.io.StdIn.readLine())
val address2: Either[String, Address] =
Address.make(scala.io.StdIn.readLine())
val person2a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
87. Refined with Smart
Constructors!
val name2: Either[String, Name] =
Name.make(scala.io.StdIn.readLine())
val email2: Either[String, Email] =
Email.make(scala.io.StdIn.readLine())
val address2: Either[String, Address] =
Address.make(scala.io.StdIn.readLine())
val person2a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
88. Refined with Smart
Constructors!
val name2: Either[String, Name] =
Name.make(scala.io.StdIn.readLine())
val email2: Either[String, Email] =
Email.make(scala.io.StdIn.readLine())
val address2: Either[String, Address] =
Address.make(scala.io.StdIn.readLine())
val person2a: Either[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
89. Refined with Smart
Constructors!
val person2b: Either[String, Person] =
for {
name <- address2 // Expected Name, found Address
email <- email2
address <- name2 // Expected Address, found Name
} yield Person(name, email, address)
90. Refined with Smart
Constructors!
val person2b: Either[String, Person] =
for {
name <- address2 // Expected Name, found Address
email <- email2
address <- name2 // Expected Address, found Name
} yield Person(name, email, address)
91.
92.
93.
94. Opaque types +
Smart Constructors
(Scala 3)
opaque type Name = String
object Name:
def make(value: String): Either[String, Name] =
if value.nonEmpty then Right(value)
else Left("Name cannot be empty")
95. Opaque types +
Smart Constructors
(Scala 3)
opaque type Email = String
object Email:
def make(value: String): Either[String, Email] =
val regex = "^[w-.]+@([w-]+.)+[w-]{2,4}$"
if value.matches(regex) then Right(value)
else Left(s"$value does not match $regex")
96. Opaque types +
Smart Constructors
(Scala 3)
opaque type Address = String
object Address:
def make(value: String): Either[String, Address] =
if value.nonEmpty then Right(value)
else Left("Address cannot be empty")
final case class Person(name: Name, email: Email, address: Address)
97. Opaque types +
Smart Constructors
(Scala 3)
opaque type Address = String
object Address:
def make(value: String): Either[String, Address] =
if value.nonEmpty then Right(value)
else Left("Address cannot be empty")
final case class Person(name: Name, email: Email, address: Address)
98. Opaque types +
Smart Constructors
(Scala 3)
opaque type Address = String
object Address:
def make(value: String): Either[String, Address] =
if value.nonEmpty then Right(value)
else Left("Address cannot be empty")
final case class Person(name: Name, email: Email, address: Address)
99.
100. Opaque types +
Smart Constructors
(Scala 3)
val person: Either[String, Person] =
for
name <- Name.make("Jorge")
email <- Email.make("jorge.vasquez@scalac.io")
address <- Address.make("100 Some St.")
yield Person(name, email, address)
101. Opaque types +
Smart Constructors
(Scala 3)
val person: Either[String, Person] =
for
name <- Name.make("")
email <- Email.make("whatever")
address <- Address.make("")
yield Person(name, email, address)
102.
103.
104. Wouldn't it be great if we could
have more precise data
modelling, without unnecessary
overhead at compile-time AND
at runtime?
131. Compile-time validations
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
132. Compile-time validations
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
133. Compile-time validations
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
134. Compile-time validations
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
135. Compile-time validations
val name1: Name = Name("Jorge")
val email1: Email = Email("jorge.vasquez@scalac.io")
val address1: Address = Address("100 Some St.")
val person1: Person = Person(name1, email1, address1)
136. Compile-time validations
val name2: Name = Name("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val email2: Email = Email("whatever") // COMPILATION ERROR! did not satisfy matches(^([w-.])*@([w-])*(.)*([w-]){2,4}$)
val address2: Address = Address("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val person2: Person = Person(name2, email2, address2)
137. Compile-time validations
val name2: Name = Name("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val email2: Email = Email("whatever") // COMPILATION ERROR! did not satisfy matches(^([w-.])*@([w-])*(.)*([w-]){2,4}$)
val address2: Address = Address("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val person2: Person = Person(name2, email2, address2)
138. Compile-time validations
val name2: Name = Name("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val email2: Email = Email("whatever") // COMPILATION ERROR! did not satisfy matches(^([w-.])*@([w-])*(.)*([w-]){2,4}$)
val address2: Address = Address("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val person2: Person = Person(name2, email2, address2)
139. Compile-time validations
val name2: Name = Name("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val email2: Email = Email("whatever") // COMPILATION ERROR! did not satisfy matches(^([w-.])*@([w-])*(.)*([w-]){2,4}$)
val address2: Address = Address("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val person2: Person = Person(name2, email2, address2)
140. Compile-time validations
val name2: Name = Name("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val email2: Email = Email("whatever") // COMPILATION ERROR! did not satisfy matches(^([w-.])*@([w-])*(.)*([w-]){2,4}$)
val address2: Address = Address("") // COMPILATION ERROR! did not satisfy hasLength(notEqualTo(0))
val person2: Person = Person(name2, email2, address2)
144. Runtime validations
val name3: Validation[String, Name] = Name.make(scala.io.StdIn.readLine())
val email3: Validation[String, Email] = Email.make(scala.io.StdIn.readLine())
val address3: Validation[String, Address] = Address.make(scala.io.StdIn.readLine())
// Short-circuiting
val person3a: Validation[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
// Non short-circuiting
val person3b: Validation[String, Person] = Validation.validateWith(name3, email3, address3)(Person.apply)
145. Runtime validations
val name3: Validation[String, Name] = Name.make(scala.io.StdIn.readLine())
val email3: Validation[String, Email] = Email.make(scala.io.StdIn.readLine())
val address3: Validation[String, Address] = Address.make(scala.io.StdIn.readLine())
// Short-circuiting
val person3a: Validation[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
// Non short-circuiting
val person3b: Validation[String, Person] = Validation.validateWith(name3, email3, address3)(Person.apply)
146. Runtime validations
val name3: Validation[String, Name] = Name.make(scala.io.StdIn.readLine())
val email3: Validation[String, Email] = Email.make(scala.io.StdIn.readLine())
val address3: Validation[String, Address] = Address.make(scala.io.StdIn.readLine())
// Short-circuiting
val person3a: Validation[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
// Non short-circuiting
val person3b: Validation[String, Person] = Validation.validateWith(name3, email3, address3)(Person.apply)
147. Runtime validations
val name3: Validation[String, Name] = Name.make(scala.io.StdIn.readLine())
val email3: Validation[String, Email] = Email.make(scala.io.StdIn.readLine())
val address3: Validation[String, Address] = Address.make(scala.io.StdIn.readLine())
// Short-circuiting
val person3a: Validation[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
// Non short-circuiting
val person3b: Validation[String, Person] = Validation.validateWith(name3, email3, address3)(Person.apply)
148. Runtime validations
val name3: Validation[String, Name] = Name.make(scala.io.StdIn.readLine())
val email3: Validation[String, Email] = Email.make(scala.io.StdIn.readLine())
val address3: Validation[String, Address] = Address.make(scala.io.StdIn.readLine())
// Short-circuiting
val person3a: Validation[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
// Non short-circuiting
val person3b: Validation[String, Person] = Validation.validateWith(name3, email3, address3)(Person.apply)
149. Runtime validations
val name3: Validation[String, Name] = Name.make(scala.io.StdIn.readLine())
val email3: Validation[String, Email] = Email.make(scala.io.StdIn.readLine())
val address3: Validation[String, Address] = Address.make(scala.io.StdIn.readLine())
// Short-circuiting
val person3a: Validation[String, Person] =
for {
name <- name3
email <- email3
address <- address3
} yield Person(name, email, address)
// Non short-circuiting
val person3b: Validation[String, Person] = Validation.validateWith(name3, email3, address3)(Person.apply)
150. Using Newtypes
// We can define methods that work with Email
def getUser(email: Email): String = Email.unwrap(email).split("@").head
val myEmail = Email("jorge.vasquez@scalac.io")
getUser(myEmail) // jorge.vasquez
151. Using Newtypes
// We can define methods that work with Email
def getUser(email: Email): String = Email.unwrap(email).split("@").head
val myEmail = Email("jorge.vasquez@scalac.io")
getUser(myEmail) // jorge.vasquez
152. Using Newtypes
// We can define methods that work with Email
def getUser(email: Email): String = Email.unwrap(email).split("@").head
val myEmail = Email("jorge.vasquez@scalac.io")
getUser(myEmail) // jorge.vasquez
153. Using Newtypes
// We can define methods that work with Email
def getUser(email: Email): String = Email.unwrap(email).split("@").head
val myEmail = Email("jorge.vasquez@scalac.io")
getUser(myEmail) // jorge.vasquez
172. Summary
• We need more precise data modelling for our
applications
• There are several options, each one with its own
limitations
173. Summary
• We need more precise data modelling for our
applications
• There are several options, each one with its own
limitations
• ZIO Prelude Smart Types provides a solution to those
limitations