Skip to content

Instantly share code, notes, and snippets.

@pfcoperez
Created December 10, 2018 17:31
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 pfcoperez/a8e31aae433319dfbf427c16d74ac57c to your computer and use it in GitHub Desktop.
Save pfcoperez/a8e31aae433319dfbf427c16d74ac57c to your computer and use it in GitHub Desktop.
// Static type
case class A(x: Int) // This is a type we can't change nor extend using inheritance. It might come from a third party library.
// Contract (can be in a completely different module or library than where `A` has been defined.
type Id = String
trait Persistence[T] { // This is a type-class: A contract with operations that can be applied to any `T`
// In this example, it represents the sets operations to read from and write to a persisent storage.
def store(id: Id, x: T): Future[Boolean] // It takes any value of type `T`
def read(id: Id): Future[Option[T]] // It tries to fetch the value of type `T` with the given id.
}
// We can add any implementation of the behaviour described by `Persistence` to `A` or any type
// by providing the implementation in the form of instances:
object InstancesOfPersistence {
implicit val evidenceOfPersistenceForA = new Persistence[A] { // Like a constant one
def store(id: Id, x: A): Future[Boolean] = Future.successful(true)
def read(id: Id): Future[A] = Future.successful(if(id == "meaning-of-life") Some(A(42)) else None)
}
}
// This can be explicitly used
InstancesOfPersistence.evidenceOfPersistenceForA.read("thx1138")
// Also, anywhere where there is an implicit value for `Persistence[A]` we can store and fetch values using that implementation.
// Examples of this kind of usage:
// A - Importing the evidence
def f: Unit = {
import InstancesOfPersistence._
implcitly[Persistence[A]].write("thx1138", A(1138))
}
// Well, this is a bit chattym isn't it? That why type-classes are usually accompanied by syntaxes, which are objects
// containing wiring from evidences to easy ways of using the type-classes' operations.
object Persistence {
object syntax {
implicit class TWithPersistenceOps[T](x: T)(implicit evidence: Persistence[T]) {
def store(id: Id): Future[Boolean] = evidence.store(id, x)
}
}
}
// Let's import the syntax too so now we can write:
def f: Unit = {
import InstancesOfPersistence._
import Persistence.syntax._
A(1138).write("thx1138")
}
// Example B - Rather than importing the evidences everywhere, we can import only when actually needed:
def f(implicit evidence: Persistence[A]) = { // This is a library
A(1138).write("thx1138")
}
def g(implicit evidence: Persistence[A]) = { // The evidence for each method is a pre-requisite
A(42).write("meaning-of-life")
}
// This is where the evidence is actually imported and fed into the library's methods
import InstancesOfPersistence._
import Persistence.syntax._
f
g
def convoluted(implicit evidence: Persistence[A]) = {
evidenve.read("aaa").map(_.map(_.store("bbb")))
f
}
convoluted
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment