class: center, middle
refined
Mark de Jong --- # /whoami
## Mark de Jong - Freelance software consultant - Functional programming enthusiast ### Website [http://vectos.net](http://vectos.net) --- class: center, middle
Refined taste?
![img](https://media.giphy.com/media/l0CLUNQQETnPhfVaU/giphy.gif) --- class: center, middle
No.
Types ofcourse! --- # Motivation for refined Imagine this typical `Person` data structure ```scala final case class Person( id: UUID, firstName: String, surname: String, phoneNumber: String, email: String, depotCode: String, numberOfCats: Int ) ``` --- # Motivation for refined - Integrity: make sure data is correct - Security: you could base64 encode a DVD in a `String` - Correctness: lower the input domain for functions --- # How can we do better? Some literal Scala a head! ## sbt setup ```scala scalaVersion := "2.13.11" libraryDependencies += "eu.timepit" %% "refined" % "0.9.17" ``` --- # Imports, make sure you remember them! ```scala import java.util.UUID import eu.timepit.refined._ import eu.timepit.refined.api._ import eu.timepit.refined.auto._ import eu.timepit.refined.generic._ import eu.timepit.refined.char._ import eu.timepit.refined.boolean._ import eu.timepit.refined.collection._ import eu.timepit.refined.numeric._ import eu.timepit.refined.string._ import eu.timepit.refined.types.numeric._ import eu.timepit.refined.types.string._ import shapeless._ ``` --- class: center, middle
Refine our primitives
--- # Refined and type literals ```scala Refined[R, P] ``` * Is used as a infix type * Where `R` is the **primitive type** * Where `P` is the **predicate** Predicates can contain primitive type literal like a `Int` or `String` ```scala type GreaterEightPredicate = Greater[8] type GreaterEight = String Refined GreaterEightPredicate ``` In scala 2.12.x there is _no_ type literals - To make this work you need to write `W."1".T` - In scala 2.13.x and 3.x you can write just `1` in a type --- # Built in type aliases Let's start simple ```scala type NonEmptyString = String Refined NonEmpty ``` ```scala type PosInt = Int Refined Positive ``` ```scala type NegInt = Int Refined Negative ``` --- # How can we do better? Lets constrain a integer to be positive at **compile time** ```scala val posInt: PosInt = 8 // posInt: PosInt = 8 ``` ```scala val posIntFail: PosInt = -1 // error: Predicate failed: (-1 > 0). // val posIntFail: PosInt = -1 // ^^ ``` --- # How can we do better? Or define a string to be non-empty at **compile time** ```scala val nonEmptyString: NonEmptyString = "Test" // nonEmptyString: NonEmptyString = Test ``` ```scala val nonEmptyStringFail: NonEmptyString = "" // error: Predicate isEmpty() did not fail. // val nonEmptyStringFail: NonEmptyString = "" // ^^ ``` --- # Runtime validation We can use `refineV` to get **runtime** validation in Scala 2.x ```scala refineV[NonEmpty]("") // res2: Either[String, Refined[String, NonEmpty]] = Left( // value = "Predicate isEmpty() did not fail." // ) ``` --- # Built in numeric predicates [`numeric`](https://github.com/fthomas/refined/blob/master/modules/core/shared/src/main/scala/eu/timepit/refined/numeric.scala) ## Comparison operators * `Less[N]` * `Greater[N]` * `Positive` * `Negative` ## Interval / ranges * `Interval.Open[L, H]` * `Interval.OpenClosed[L, H]` * `Interval.ClosedOpen[L, H]` * `Interval.Closed[L, H]` --- # Built in numeric predicates examples ```scala val intervalOpen: Int Refined Interval.Open[1, 10] = 2 // intervalOpen: Refined[Int, Interval.Open[1, 10]] = 2 ``` ```scala val greater: Int Refined Greater[3] = 4 // greater: Refined[Int, Greater[3]] = 4 ``` --- # Built in numeric predicates examples ```scala val intervalOpenFail: Int Refined Interval.Open[1, 10] = 1 // error: Left predicate of ((1 > 1) && (1 < 10)) failed: Predicate failed: (1 > 1). // val intervalOpenFail: Int Refined Interval.Open[1, 10] = 1 // ^ ``` ```scala val greaterFail: Int Refined Greater[3] = 2 // error: Predicate failed: (2 > 3). // val greaterFail: Int Refined Greater[3] = 2 // ^ ``` --- # Built in string predicates [`string`](https://github.com/fthomas/refined/blob/master/modules/core/shared/src/main/scala/eu/timepit/refined/string.scala) ## Matching Support for predicates like `StartsWith[S]`, `MatchesRegex[S]`, `EndsWith[S]` ## Formats There is support for `Uri`, `Url`, `Xml`, `XPath`, `Regex`, `JsonPath`, `IPv4`, `IPv6` --- # Built in string predicates examples ```scala val endsWith: String Refined EndsWith["test"] = "Testtest" // endsWith: Refined[String, EndsWith[test]] = Testtest ``` ```scala val ipv4: String Refined IPv4 = "212.22.33.1" // ipv4: Refined[String, IPv4] = 212.22.33.1 ``` --- # Built in string predicates examples ```scala val endsWithFail: String Refined EndsWith["test"] = "Tester" // error: Predicate failed: "Tester".endsWith("test"). // val endsWithFail: String Refined EndsWith["test"] = "Tester" // ^^^^^^^^ ``` ```scala val ipv4Fail: String Refined IPv4 = "212.22.33.288" // error: Predicate failed: 212.22.33.288 is a valid IPv4. // val ipv4Fail: String Refined IPv4 = "212.22.33.288" // ^^^^^^^^^^^^^^^ ``` --- # Built in predicates [`collection`](https://github.com/fthomas/refined/blob/master/modules/core/shared/src/main/scala/eu/timepit/refined/collection.scala) ## Empty or NonEmpty? Two predicates `Empty` or `NonEmpty` ## Existential predicates Existenial predicates like `Forall[P]` and `Exists[P]`, note `P` is a predicate! ## Size predicates Predicates like `Size[P]`, `MinSize[N]` and `MaxSize[N]` --- # Built in collection predicates examples ```scala refineV[NonEmpty](List("test")) // res7: Either[String, Refined[List[String], NonEmpty]] = Right( // value = List(test) // ) ``` ```scala refineV[Forall[NonEmpty]](List("test")) // res8: Either[String, Refined[List[String], Forall[NonEmpty]]] = Right( // value = List(test) // ) ``` --- # Built in collection predicates examples ```scala refineV[NonEmpty](List.empty[String]) // res9: Either[String, Refined[List[String], NonEmpty]] = Left( // value = "Predicate isEmpty(List()) did not fail." // ) ``` ```scala refineV[Forall[NonEmpty]](List("")) // res10: Either[String, Refined[List[String], Forall[NonEmpty]]] = Left( // value = "Predicate failed: (!isEmpty())." // ) ``` --- # Predicate operators [`boolean`](https://github.com/fthomas/refined/blob/master/modules/core/shared/src/main/scala/eu/timepit/refined/boolean.scala) * `Not[P]`: negation * `And[A, B]`: conjunction * `Or[A, B]`: disjunction * `Xor[A, B]`: exclusive disjunction * `AnyOf[PS]`: disjunction --- # Built in boolean predicates examples ```scala refineV[NonEmpty And Forall[NonEmpty]](List("test")) // res11: Either[String, Refined[List[String], And[NonEmpty, Forall[NonEmpty]]]] = Right( // value = List(test) // ) ``` ```scala refineV[AnyOf[Digit :: Letter :: HNil]]('c') // res12: Either[String, Refined[Char, AnyOf[Digit :: Letter :: HNil]]] = Right( // value = c // ) ``` --- # Built in boolean predicates examples ```scala refineV[NonEmpty And Forall[NonEmpty]](List.empty[String]) // res13: Either[String, Refined[List[String], And[NonEmpty, Forall[NonEmpty]]]] = Left( // value = "Left predicate of (!isEmpty(List()) && ()) failed: Predicate isEmpty(List()) did not fail." // ) ``` ```scala refineV[AnyOf[Digit :: Letter :: HNil]]('#') // res14: Either[String, Refined[Char, AnyOf[Digit :: Letter :: HNil]]] = Left( // value = "Predicate failed: (isDigit('#') || isLetter('#') || false)." // ) ``` --- # Let's refine the types Let's create some type aliases ```scala type Code = String Refined Size[Equal[3]] type PhoneNumber = String Refined MatchesRegex["([0-9]{10})"] type Email = String Refined MatchesRegex["^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"] ``` --- # Reviewed case class ```scala case class Person( id: UUID, firstName: NonEmptyString, surname: NonEmptyString, phoneNumber: PhoneNumber, email: Email, depotCode: Code, numberOfCats: PosInt ) ``` --- class: center, middle
Job done?
![img](https://media.giphy.com/media/WZTgoOuIs63QEV8IFn/giphy-downsized.gif) --- class: center, middle
What about integrations?
--- # Integration modules available For popular JSON, CSV and database libraries! * [circe-refined](https://github.com/circe/circe) * [doobie-refined](https://github.com/tpolecat/doobie) * [kantan.csv-refined](https://nrinaudo.github.io/kantan.csv/refined.html) --- class: center, middle
Do it yourself!
![img](https://media.giphy.com/media/wotqBCVmRVn2w/giphy.gif) --- # Integrate codecs yourself ```scala implicit final def refinedDecoder[T, P, F[_, _]](implicit underlying: Decoder[T], validate: Validate[T, P], refType: RefType[F] ): Decoder[F[T, P]] = Decoder.instance { c => underlying(c) match { case Right(t0) => refType.refine(t0) match { case Left(err) => Left(DecodingFailure(err, c.history)) case r @ Right(t) => r.asInstanceOf[Decoder.Result[F[T, P]]] } case l @ Left(_) => l.asInstanceOf[Decoder.Result[F[T, P]]] } } ``` --- # Custom predicates ```scala import eu.timepit.refined.api.Validate import org.iban4j.Iban case class IbanCode() object IbanCode { implicit val ibanCodeValidate: Validate[String, IbanCode] = Validate.fromPartial(str => Iban.valueOf(str), "IBAN", IbanCode()) } ``` ```scala refineV[IbanCode]("NL91ABNA0417164300") // res15: Either[String, Refined[String, IbanCode]] = Right( // value = NL91ABNA0417164300 // ) refineV[IbanCode]("") // res16: Either[String, Refined[String, IbanCode]] = Left( // value = "IBAN predicate failed: Empty string can't be a valid Iban." // ) ``` --- class: center, middle
Challenges?
![img](https://media.giphy.com/media/l41Ym49ppcDP6iY3C/giphy.gif) --- # Challenges? ### Lots of refined types Slow compilation performance on projects with lots of refined types in Scala 2.x. **Might be better in Scala 3 or keep projects small** ### Composing refinements ```scala type Hostname = NonEmptyString type Port = PortNumber type HostAddress = ??? // What about (NonEmptyString + ":" + PortNumber) ? ``` **Split it up** ```scala final case class HostAddress(name: NonEmptyString, port: PortNumber) ``` --- # Challenges? ### Operations on refinements String operations or numeric operations have limited support. **For now use the underlying primitive, when you are pretty sure it's safe** --- class: center, middle
Questions?
--- # Fin ### Slides [http://fristi.github.com/refined-deck](http://fristi.github.com/refined-deck) ### Libraries mentioned - refined at [https://github.com/fthomas/refined](https://github.com/fthomas/refined)