Skip to content

Instantly share code, notes, and snippets.

@arturaz
Created October 10, 2022 18:25
Show Gist options
  • Save arturaz/270d821a8171d3940a0c610e472f8c88 to your computer and use it in GitHub Desktop.
Save arturaz/270d821a8171d3940a0c610e472f8c88 to your computer and use it in GitHub Desktop.
Code for "Scala =:= and implicits demystified " video (https://www.youtube.com/watch?v=4jrHAmKx7YE)
package app.implicit_constraints
object Bounds1 {
case class Container[A](a: A)
implicit class IntContainerExtensions(container: Container[Int]) {
def addWithExtension(other: Int): Int = container.a + other
}
val intContainer: Container[Int] = Container(5)
val someInt1: Int = new IntContainerExtensions(intContainer).addWithExtension(10)
val someInt2: Int = intContainer.addWithExtension(10)
val stringContainer: Container[String] = Container("5")
// new IntContainerExtensions(stringContainer).addWithExtension(10)
// stringContainer.addWithExtension(10)
}
package app.implicit_constraints
object Bounds2 {
implicit class IntContainerExtensions(container: Container[Int]) {
def addWithExtension(other: Int): Int = container.a + other
}
case class Container[A](a: A) {
/**
* @param evidence function that turns [[A]] to [[Int]].
*/
def addWithConstraint(other: Int)(implicit evidence: A =:= Int /** same as `=:=[A, Int]` */): Int = {
a + other
}
}
val someInt: Int = Container(5).addWithConstraint(10)
// Container("5").addWithConstraint(10)
// Container("5").addWithConstraint(10)(<:<.refl[Int])
Container(5).addWithConstraint(10)
}
package app.implicit_constraints
object Bounds3 {
class Animal {
def speak: String = "Grmh"
}
class Dog extends Animal {
override def speak = "Woof!"
}
case class Container[A](a: A) {
/**
* @param evidence function that turns [[A]] to [[Animal]].
*/
def extractSpeechWithConstraint(implicit evidence: A <:< Animal): String = {
evidence(a).speak
}
}
implicit class AnimalContainerExtensions(container: Container[Animal]) {
def extractSpeechWithExtension: String = container.a.speak
}
implicit class AnimalContainerExtensions2[A <: Animal](val container: Container[A]) extends AnyVal {
def extractSpeechWithExtension: String = container.a.speak
}
val someString1: String = Container(new Animal).extractSpeechWithExtension
val someString4: String = Container(new Dog).extractSpeechWithExtension
val someString2: String = Container(new Dog).extractSpeechWithConstraint
val someString3: String = Container(new Animal).extractSpeechWithConstraint
}
package app.implicit_constraints
import app.Functor
object Bounds4 {
case class Container[A](a: A) {
/**
* Extracts the 'context' from the inner layer
* (like `Container[ Option[Int] ]`) into the outer layer
* (like `Option[ Container[Int] ]`).
*
* @param evidence function that turns [[A]] into `F[B]`.
* @param functor provides the way to turn `F[A]` into `F[SomethingElse]`.
* @tparam F type of 'context', like `Option[A]`, `List[A]`, `Future[A]`, etc.
* @tparam B type of value in the 'context', like [[Int]], [[String]], etc.
* @return container of the value [[B]] wrapped in the 'context' [[F]].
*/
def extract[F[_], B](implicit evidence: A =:= F[B], functor: Functor[F]): F[Container[B]] = {
val fb: F[B] = evidence(a)
val fContainerB: F[Container[B]] = functor.map(fb) { (b: B) =>
Container(b)
}
fContainerB
}
}
object Container {
implicit val functor: Functor[Container] = new Functor[Container] {
override def map[A, B](container: Container[A])(f: A => B) = Container(f(container.a))
}
}
val optionOfContainer: Option[Container[Int]] = Container(Option(5)).extract(<:<.refl, Functor.optionFunctor)
val optionOfContainer1: Option[Container[Int]] = Container(Option(5)).extract
val listOfContainer: List[Container[Int]] = Container(List(1, 2, 3)).extract(<:<.refl, Functor.listFunctor)
// Container("5").extract
// Container("5").extract(<:<.refl[String], ???)
// Container("5").extract(<:<.refl[Option[String]], ???)
}
package app
/**
* @tparam F any generic type that has 1 type parameter itself, like `List[A]`, `Option[A]`, `Vector[A]`, `Future[A]`
* and so on.
*/
trait Functor[F[_]] {
/** Given an instance of `F[A]` and a function that can turn [[A]] into [[B]] returns `F[B]`. */
def map[A, B](fa: F[A])(f: A => B): F[B]
}
object Functor {
implicit val optionFunctor: Functor[Option] = new Functor[Option] {
override def map[A, B](fa: Option[A])(f: A => B) = fa match {
case None => None
case Some(value) => Some(f(value))
}
}
implicit val listFunctor: Functor[List] = new Functor[List] {
override def map[A, B](fa: List[A])(f: A => B) = fa match {
case Nil => Nil
case head :: next => f(head) :: map(next)(f)
}
}
implicit val vectorFunctor: Functor[Vector] = new Functor[Vector] {
override def map[A, B](fa: Vector[A])(f: A => B) = fa.map(f)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment