Last active
February 19, 2019 10:58
-
-
Save dcastro/0ca0e95e7b8a2b975eda066c9d79b971 to your computer and use it in GitHub Desktop.
Refinement Types in Scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import eu.timepit.refined._ | |
import eu.timepit.refined.api._ | |
import eu.timepit.refined.auto._ | |
import eu.timepit.refined.numeric._ | |
import eu.timepit.refined.boolean._ | |
import eu.timepit.refined.char._ | |
import eu.timepit.refined.collection._ | |
import eu.timepit.refined.generic._ | |
import eu.timepit.refined.string._ | |
/** | |
* Refined Types: https://github.com/fthomas/refined | |
* | |
* You'll need at least these two libraries: | |
* "eu.timepit" %% "refined" % refinedVersion | |
* "io.circe" %% "circe-refined" % circeVersion | |
*/ | |
/** | |
* The refined library lets us *constrain* the set of possible values for a type. | |
* A `Refined[Int, Positive]` is an integer that can only ever be positive. | |
*/ | |
// We can do this using the `refineMV` macro explicitly. | |
// This will check that the number literal `1` is indeed positive, during compilation. | |
import eu.timepit.refined.refineMV | |
val b: Refined[Int, Positive] = refineMV(1) | |
// Or we can use an implicit macro: | |
import eu.timepit.refined.auto._ | |
val a: Refined[Int, Positive] = 1 | |
// Assigning non-positive integers will not type-check, i.e. compilation will fail. | |
// val c: Refined[Int, Positive] = -2 | |
// val c: Refined[Int, Positive] = refineMV(-2) | |
/** | |
* `Refined[Int, Positive]` can also be written as `Int Refined Positive` in infix notation. | |
*/ | |
// W.`2`.T is a type that represents the number 2 | |
val e: Int Refined Greater[W.`2`.T] = 3 | |
val f: Int Refined (Greater[W.`2`.T] And Less[W.`10`.T]) = 8 | |
val g: Int Refined Interval.Closed[W.`2`.T, W.`10`.T] = 8 | |
val h: String Refined MatchesRegex[W.`".*localhost.*"`.T] = "localhost" | |
val i: String Refined Url = "http://localhost" | |
val j: String Refined MaxSize[W.`16`.T] = "abcdefghijklmnop" | |
val k: String Refined NonEmpty = "1" | |
/** | |
* The implicit and the explicit `refineMV` macros work only on values available at compile-time, | |
* like string and number literals, e.g. "hello", 2, 5.0, true, etc. | |
* | |
* To validate values not available at compile-time (e.g., values coming from I/O such as HTTP requests, | |
* the terminal, a file, etc), use the `refineV` function. | |
* | |
* Because `refineV` validates values at runtime instead of at compile-time, | |
* it returns an `Either[String, SomeRefinedType]`, where `String` is an error message. | |
*/ | |
def readIntFromFile(): Int = 3 | |
def readUrlsFromFile(): List[String] = List("http://localhost", "http://google.com") | |
val result1: Either[String, Int Refined Positive] = | |
refineV(readIntFromFile()) | |
val result2: Either[String, List[String] Refined Forall[Url]] = | |
refineV(readUrlsFromFile()) | |
/** | |
* Add this import to generate circe encoders/decoders for refined types. | |
* import io.circe.refined._ | |
*/ | |
import io.circe._ | |
import io.circe.parser._ | |
import io.circe.refined._ | |
import io.circe.generic.semiauto._ | |
case class C( | |
xs: List[Int] Refined Forall[Greater[W.`2`.T]] | |
) | |
object C { | |
implicit val decodeC: Decoder[C] = deriveDecoder | |
} | |
// parses successfully | |
decode[C]( | |
""" | |
{ "xs": [3, 9, 4] } | |
""") | |
// fails decoding | |
decode[C]( | |
""" | |
{ "xs": [0, 0] } | |
""") | |
/** | |
* Use `.value` to discard a refinement and access the underlying "raw" type. | |
*/ | |
val refinedInt: Int Refined Positive = 3 | |
val unrefinedInt: Int = refinedInt.value |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment