Skip to content

Instantly share code, notes, and snippets.

@frgomes
Last active January 5, 2023 12:03
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 frgomes/eb8efc3224fb102a079dc2fb34ccf630 to your computer and use it in GitHub Desktop.
Save frgomes/eb8efc3224fb102a079dc2fb34ccf630 to your computer and use it in GitHub Desktop.
Scala - typeclasses - simple tutorial
// QUICK EXAMPLE FOR THE IMPATIENT
/** This trait provides ability to instantiate a generic class */
trait Makeable[T] {
def make: T
}
/** This typeclass which allows making an instance of type `T` */
class Make[T: Makeable] {
def make(): T = implicitly[Makeable[T]].make
}
// implicit helper objects for typeclass `Make`
implicit object MakeableString extends Makeable[String] { def make: String = "" }
implicit object MakeableString extends Makeable[Int] { def make: Int = 0 }
implicit object MakeableString extends Makeable[Long] { def make: Long = 0L }
implicit object MakeableString extends Makeable[Float] { def make: Float = 0.0f }
implicit object MakeableString extends Makeable[Double] { def make: Double = 0.0 }
// TUTORIAL WITH EXPLANATIONS FOR EACH STEP
/*
* The first step when defining a typeclass consists on
* defining the trait it implements.
*/
trait Size[A] {
def size(a: A): Int
}
/*
* The second step when defining a typeclass consists on
* defining a companion object for the typeclass trait giving some useful default implementations.
*
* These useful default implementations are provided as implicit objects and/or implicit functions, which
* can be found after local implicits so that you can still override the default implementations if you wish.
*
* See more about implicit search order here:
* https://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits/5598107#5598107
*/
object Size {
implicit object IntSize extends Size[Int] { def size(a: Int) = a }
implicit object StringSize extends Size[String] { def size(a: String) = a.length }
/* This one is more elaborated: SizeLike[List[A]] needs SizeLike[A], which can be found implicitly. */
implicit def ListSize[A](implicit itemSized: Size[A]) = new Size[List[A]] {
def size(a: List[A]): Int = a.map(item => itemSized.size(item)).sum
}
}
/*
* The third step (and the last step!) when defining a typeclass consists on
* defining a convenience trait, useful on the calling site, which requires implicit argument(s).
*
* This step is not a requirement but usually provided as a sake of convenience.
*
* Notice how this trait is similar to the typeclass trait.
* It usually only wires an implicit argument and employs it in order to arrive to a concrete implementation.
*/
trait SizeImpl {
def size[A](a: A)(implicit o: Size[A]): Int = o.size(a)
}
//-----
// This is a simple example which uses the typeclass
object Example1 extends SizeImpl {
def main = {
println(size(2)) // prints 2
println(size("abc")) // prints 3
println(size(List("abc", "d", "efgh"))) // prints 8
}
}
Example1.main
//-----
// This example supplies an implicit implementation
object Example2 extends SizeImpl {
implicit object BooleanSize extends Size[Boolean] { def size(a: Boolean) = if(a) 1 else 0 }
def main = {
println(size(false)) // prints 0
println(size(true)) // prints 1
}
}
Example2.main
//-----
// This example supplies an implicit implementation which is used by a default implementation
object Example3 extends SizeImpl {
implicit object BooleanSize extends Size[Boolean] { def size(a: Boolean) = if(a) 1 else 0 }
def main = {
println(size(List(true, false, true, false, true))) // prints 3
}
}
Example3.main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment