O slideshow foi denunciado.
Seu SlideShare está sendo baixado. ×

Be Smart, Constrain Your Types to Free Your Brain!

Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Carregando em…3
×

Confira estes a seguir

1 de 178 Anúncio

Be Smart, Constrain Your Types to Free Your Brain!

Baixar para ler offline

Admit it: You have used a String to model email values, even though most strings aren’t valid emails (don’t worry, we all have!). Imprecise data models are easy, but they crash applications and corrupt external systems. On the other hand, precise data models take time and generate boilerplate.

So-called newtype libraries have stepped up to the challenge, making it easier to model data precisely using runtime validation. However, newtype libraries aren’t able to validate constants at compile-time, and they don’t generally work with Scala 3.

Enter ZIO Prelude Smart Types, which make it simple to model data types precisely, without any boilerplate, runtime overhead, or compile-time overhead. ZIO Prelude Smart Types work at compile-time and runtime, and they have a uniform API across Scala 2 & 3.

Join ZIO Prelude contributor Jorge Vásquez as he teaches you how to be smart, and constrain your types to free your brain!

Admit it: You have used a String to model email values, even though most strings aren’t valid emails (don’t worry, we all have!). Imprecise data models are easy, but they crash applications and corrupt external systems. On the other hand, precise data models take time and generate boilerplate.

So-called newtype libraries have stepped up to the challenge, making it easier to model data precisely using runtime validation. However, newtype libraries aren’t able to validate constants at compile-time, and they don’t generally work with Scala 3.

Enter ZIO Prelude Smart Types, which make it simple to model data types precisely, without any boilerplate, runtime overhead, or compile-time overhead. ZIO Prelude Smart Types work at compile-time and runtime, and they have a uniform API across Scala 2 & 3.

Join ZIO Prelude contributor Jorge Vásquez as he teaches you how to be smart, and constrain your types to free your brain!

Anúncio
Anúncio

Mais Conteúdo rRelacionado

Diapositivos para si (17)

Semelhante a Be Smart, Constrain Your Types to Free Your Brain! (20)

Anúncio

Mais recentes (20)

Anúncio

Be Smart, Constrain Your Types to Free Your Brain!

  1. 1. Be Smart, Constrain Your Types to Free Your Brain! Functional Scala December 3rd, 2021
  2. 2. Jorge Vásquez Scala Developer @Scalac ZIO Prelude contributor
  3. 3. Background ZIO Prelude is a Scala-first take on functional abstractions
  4. 4. Background ZIO Prelude is a Scala-first take on functional abstractions • Type classes to describe the ways different types are similar
  5. 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. 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
  7. 7. Problem Precise Data Modelling
  8. 8. Representing everything with Simple Types final case class Person( name: String, email: String, address: String )
  9. 9. Representing everything with Simple Types final case class Person( name: String, email: String, address: String )
  10. 10. Representing everything with Simple Types final case class Person( name: String, email: String, address: String )
  11. 11. Representing everything with Simple Types final case class Person( name: String, email: String, address: String )
  12. 12. Representing everything with Simple Types final case class Person( name: String, email: String, address: String )
  13. 13. Storing invalid data in databases name email address t1@tst.co whatever Main Av. t2@tst.co
  14. 14. Applications corrupting third party systems
  15. 15. Applications throwing exceptions
  16. 16. 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") }
  17. 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. 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. 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. 20. 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") } }
  21. 21. 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)
  22. 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. 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. 24. 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)
  25. 25. 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)
  26. 26. 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)
  27. 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. 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. 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. 30. Smart Constructors val person: Either[String, Person] = for { name <- Name.make("") email <- Email.make("whatever") address <- Address.make("") } yield Person(name, email, address)
  31. 31. Smart Constructors val person: Either[String, Person] = for { name <- Name.make("") email <- Email.make("whatever") address <- Address.make("") } yield Person(name, email, address)
  32. 32. Smart Constructors val person: Either[String, Person] = for { name <- Name.make("") email <- Email.make("whatever") address <- Address.make("") } yield Person(name, email, address)
  33. 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. 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. 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. 36. 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") }
  37. 37. 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") } }
  38. 38. 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)
  39. 39. 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)
  40. 40. 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)
  41. 41. 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)
  42. 42. Newtype library val person: Either[String, Person] = for { name <- Name.make("") email <- Email.make("whatever") address <- Address.make("") } yield Person(name, email, address)
  43. 43. 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)
  44. 44. 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)
  45. 45. 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)
  46. 46. 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)
  47. 47. 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)
  48. 48. 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)
  49. 49. 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)
  50. 50. 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)
  51. 51. 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)
  52. 52. 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)
  53. 53. 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)
  54. 54. 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)
  55. 55. 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)
  56. 56. 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)
  57. 57. 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)
  58. 58. 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)
  59. 59. Compile-time validations with Refined val unknownName: Name = scala.io.StdIn.readLine() // Compile-time refinement only works with literals
  60. 60. Compile-time validations with Refined val unknownName: Name = scala.io.StdIn.readLine() // Compile-time refinement only works with literals
  61. 61. Compile-time validations with Refined val unknownName: Name = scala.io.StdIn.readLine() // Compile-time refinement only works with literals
  62. 62. 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)
  63. 63. 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)
  64. 64. 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)
  65. 65. 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)
  66. 66. 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)
  67. 67. 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)
  68. 68. 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(_)) }
  69. 69. 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(_)) }
  70. 70. 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)
  71. 71. 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)
  72. 72. 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)
  73. 73. 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)
  74. 74. 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)
  75. 75. 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)
  76. 76. 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)
  77. 77. 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)
  78. 78. 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)
  79. 79. 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)
  80. 80. 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)
  81. 81. 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)
  82. 82. 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)
  83. 83. 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)
  84. 84. 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")
  85. 85. 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")
  86. 86. 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)
  87. 87. 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)
  88. 88. 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)
  89. 89. 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)
  90. 90. 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)
  91. 91. Wouldn't it be great if we could have more precise data modelling, without unnecessary overhead at compile-time AND at runtime?
  92. 92. Presenting ZIO Prelude Smart Types
  93. 93. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Name extends Newtype[String] { def assertion = assert(!isEmptyString) } type Name = Name.Type
  94. 94. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Name extends Newtype[String] { def assertion = assert(!isEmptyString) } type Name = Name.Type
  95. 95. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Name extends Newtype[String] { def assertion = assert(!isEmptyString) } type Name = Name.Type
  96. 96. Smart Types in Scala 2! import zio.prelude._ import Assertion._ object Email extends Newtype[String] { override def assertion = assert { matches("^([w-.])*@([w-])*(.)*([w-]){2,4}$".r) } } type Email = Email.Type
  97. 97. Smart Types in Scala 2! import zio.prelude._ import Assertion._ object Email extends Newtype[String] { override def assertion = assert { matches("^([w-.])*@([w-])*(.)*([w-]){2,4}$".r) } } type Email = Email.Type
  98. 98. Smart Types in Scala 2! import zio.prelude._ import Assertion._ object Email extends Newtype[String] { override def assertion = assert { matches("^([w-.])*@([w-])*(.)*([w-]){2,4}$".r) } } type Email = Email.Type
  99. 99. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Email extends Newtype[String] { override def assertion = assert { matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } } } type Email = Email.Type
  100. 100. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Email extends Newtype[String] { override def assertion = assert { matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } } } type Email = Email.Type
  101. 101. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Email extends Newtype[String] { override def assertion = assert { matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } } } type Email = Email.Type
  102. 102. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Address extends Newtype[String] { def assertion = assert(!isEmptyString) } type Address = Address.Type
  103. 103. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Address extends Newtype[String] { def assertion = assert(!isEmptyString) } type Address = Address.Type
  104. 104. Smart Types in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Address extends Newtype[String] { def assertion = assert(!isEmptyString) } type Address = Address.Type
  105. 105. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Name extends Newtype[String]: override inline def assertion = !isEmptyString type Name = Name.Type
  106. 106. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Name extends Newtype[String]: override inline def assertion = !isEmptyString type Name = Name.Type
  107. 107. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Name extends Newtype[String]: override inline def assertion = !isEmptyString type Name = Name.Type
  108. 108. Smart Types in Scala 3! import zio.prelude.* import Assertion.* object Email extends Newtype[String]: override inline def assertion = matches("^([w-.])*@([w-])*(.)*([w-]){2,4}$".r) type Email = Email.Type
  109. 109. Smart Types in Scala 3! import zio.prelude.* import Assertion.* object Email extends Newtype[String]: override inline def assertion = matches("^([w-.])*@([w-])*(.)*([w-]){2,4}$".r) type Email = Email.Type
  110. 110. Smart Types in Scala 3! import zio.prelude.* import Assertion.* object Email extends Newtype[String]: override inline def assertion = matches("^([w-.])*@([w-])*(.)*([w-]){2,4}$".r) type Email = Email.Type
  111. 111. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Email extends Newtype[String]: override inline def assertion = matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } type Email = Email.Type
  112. 112. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Email extends Newtype[String]: override inline def assertion = matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } type Email = Email.Type
  113. 113. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Email extends Newtype[String]: override inline def assertion = matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } type Email = Email.Type
  114. 114. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Address extends Newtype[String]: override inline def assertion = !isEmptyString type Address = Address.Type
  115. 115. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Address extends Newtype[String]: override inline def assertion = !isEmptyString type Address = Address.Type
  116. 116. Smart Types in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Address extends Newtype[String]: override inline def assertion = !isEmptyString type Address = Address.Type
  117. 117. 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)
  118. 118. 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)
  119. 119. 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)
  120. 120. 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)
  121. 121. 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)
  122. 122. 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)
  123. 123. 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)
  124. 124. 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)
  125. 125. 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)
  126. 126. 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)
  127. 127. Compile-time validations val unknownName: Name = Name(scala.io.StdIn.readLine()) // COMPILATION ERROR! Could not validate Assertion at compile-time
  128. 128. Compile-time validations val unknownName: Name = Name(scala.io.StdIn.readLine()) // COMPILATION ERROR! Could not validate Assertion at compile-time
  129. 129. Compile-time validations val unknownName: Name = Name(scala.io.StdIn.readLine()) // COMPILATION ERROR! Could not validate Assertion at compile-time
  130. 130. 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)
  131. 131. 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)
  132. 132. 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)
  133. 133. 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)
  134. 134. 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)
  135. 135. 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)
  136. 136. 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
  137. 137. 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
  138. 138. 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
  139. 139. 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
  140. 140. Using Newtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  141. 141. Using Newtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  142. 142. Using Newtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  143. 143. Using Newtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  144. 144. Defining Subtypes in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Email extends Subtype[String] { override def assertion = assert { matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } } } type Email = Email.Type
  145. 145. Defining Subtypes in Scala 2! import zio.prelude._ import Assertion._ import Regex._ object Email extends Subtype[String] { override def assertion = assert { matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } } } type Email = Email.Type
  146. 146. Defining Subtypes in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Email extends Subtype[String]: override inline def assertion = matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } type Email = Email.Type
  147. 147. Defining Subtypes in Scala 3! import zio.prelude.* import Assertion.* import Regex.* object Email extends Subtype[String]: override inline def assertion = matches { start ~ anyRegexOf(alphanumeric, literal("-"), literal(".")).+ ~ literal("@") ~ anyRegexOf(alphanumeric, literal("-")).+ ~ literal(".").+ ~ anyRegexOf(alphanumeric, literal("-")).between(2, 4) ~ end } type Email = Email.Type
  148. 148. Using Subtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  149. 149. Using Subtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  150. 150. Using Subtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  151. 151. Using Subtypes def capitalize(str: String): String = str.capitalize val myEmail = Email("jorge.vasquez@scalac.io") capitalize(myEmail)
  152. 152. Summary
  153. 153. Summary
  154. 154. Summary • We need more precise data modelling for our applications
  155. 155. Summary • We need more precise data modelling for our applications • There are several options, each one with its own limitations
  156. 156. 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
  157. 157. Summary Smart Const. Opaque types + Smart Const. Newtype Refined + Smart Const. ZIO Prelude Smart Types Supported in Scala 2 √ √ √ √ Supported in Scala 3 √ √ √ Bye compile-time overhead! √ √ Bye runtime overhead! √ √ √
  158. 158. Special thanks
  159. 159. Special thanks • Ziverge for organizing this conference
  160. 160. Special thanks • Ziverge for organizing this conference • John De Goes, Kit Langton and Adam Fraser for guidance and support
  161. 161. Contact me @jorvasquez2301 jorge-vasquez-2301 jorge.vasquez@scalac.io

×