Created
December 10, 2018 17:31
-
-
Save pfcoperez/a8e31aae433319dfbf427c16d74ac57c to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// 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