Skip to content

Instantly share code, notes, and snippets.

@dcastro
Last active February 19, 2019 10:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcastro/0ca0e95e7b8a2b975eda066c9d79b971 to your computer and use it in GitHub Desktop.
Save dcastro/0ca0e95e7b8a2b975eda066c9d79b971 to your computer and use it in GitHub Desktop.
Refinement Types in Scala
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