Skip to content

Instantly share code, notes, and snippets.

@gigiigig
Last active October 21, 2016 11:09
Show Gist options
  • Save gigiigig/1a6925c0bd2b5529a315 to your computer and use it in GitHub Desktop.
Save gigiigig/1a6925c0bd2b5529a315 to your computer and use it in GitHub Desktop.
import scalaz._
import Scalaz._
/** I think there are two use cases where implicits are very useful:
* TypeClasses
* Pimp or Rich pattern
*
* Let's start with typeclasses, probably the more popular,
* e.g. Collection library in Scala is all TC based, look for CanBuildFrom
*/
object typeclasses extends App {
/** Let's suppose we have a function `foo`
*/
object v1 {
def foo(l: List[Int]): Int = {
l.foldLeft(0)(_ + _)
}
val res = foo(List(1,2,3))
println(s"res: ${res}")
}
/** Cool we can fold a List and reduce it, but this is a common operation,
* we want to abstract this a little bit
*/
object v2 {
// We introduce a type paramter `T`, now we have `List[T]`
// which is more generic, and we can pass a funtion `f` that tells us
// how to reduce the 2 elements
def foo[T](l: List[T])(i: T, f: (T, T) => T): T = {
l.foldLeft(i)(f)
}
val res = foo(List(1,2,3))(0, (a, b) => a + b)
println(s"res: ${res}")
}
/** Using a function is cool but we still have to pass it around every time,
* we can do better
*/
object v3 {
// We come from Java, we need an interface!
trait Addable[T] {
def zero: T
def add(t1: T, t2: T): T
}
// We create an implementation for each type we want to use that function with,
// this is a bit tidier IMO
object Addable {
val intAddable = new Addable[Int] {
val zero = 0
def add(t1: Int, t2: Int) = t1 + t2
}
}
def foo[T](l: List[T])(T: Addable[T]): T = {
l.foldLeft(T.zero)(T.add)
}
// But yes, we still have to pass it every time
val res = foo(List(1,2,3))(Addable.intAddable)
println(s"res: ${res}")
}
/** But we have a way to pass things around in Scala without passing
* them right ?
*/
object v4 {
trait Addable[T] {
def zero: T
def add(t1: T, t2: T): T
}
object Addable {
// Don't worry much about this, we'll see later
def apply[T](implicit at: Addable[T]) = at
implicit val intAddable = new Addable[Int] {
val zero = 0
def add(t1: Int, t2: Int) = t1 + t2
}
}
// Let's make it implicit
def foo[T](l: List[T])(implicit T: Addable[T]): T = {
l.foldLeft(T.zero)(T.add)
}
// We don't have to pass it anymore, and do you notice that
// I've never imported `Adable`?
// That's because scala support this "pattern" called TypeClass, it will search
// for implicits in the companion object of all the types involved in the implicit
// in this case `Addable`, and `Int` if there was one.
val res = foo(List(1,2,3))
println(s"res: ${res}")
// This "pattern" is so common that there is a special syntax for it, called "Context Bound"
// This is the same as above, but we need to use implicitly to get the implicit this time,
// this is why we creted that apply funtion in the `Addable` object.
def foo2[T : Addable](l: List[T]): T = {
l.foldLeft(Addable[T].zero)(Addable[T].add)
}
}
/** Addable was a terrible name right?
* Let's call it what it is, it's a `Monoid`,
* and because it's a commond concept, somebody already defined
* many TypeClasses for the common types, so we need to import scalaz, and ...
*/
object v5 {
// This will work for every type for which we have a Monoid instance
def foo[T](l: List[T])(implicit T: Monoid[T]): T = {
l.foldLeft(T.zero)(T.append(_, _))
}
val res = foo(List(1,2,3))
println(s"res: ${res}")
}
/** Even folding is a common operation,
* we might want to use this funtio with some custom data structure
* that can be folded, and that is not in the same class hierachy
* of List
*/
object v6 {
// let's use the TypeClass `Foldable`
def foo[F[_], T](l: F[T])(implicit T: Monoid[T], F: Foldable[F]): T = {
F.foldLeft(l, T.zero)(T.append(_, _))
}
// At this point I hope you are sold :D
val res = foo(List(1,2,3))
println(s"res: ${res}")
}
}
/** Now the Pimp/Rich pattern, the main goal here is
* to avoid to pollute the model with logic, I think putting logic in
* the model is bad idea, keeping only data in the model
* makes easier to share it. We can still add functions to 'pure' models
* using implicits.
*/
object pimp {
object v1 {
// That's what usually happens,
// but different parts of the project need different functionalities,
// and the model gets polluted
case class Foo(i: Int) {
def print = println(s"Foo with $i")
}
}
object v2 {
// Let's keep it clean
case class Foo(i: Int)
// common functions can be in the companion,
// much easier to refactor
object Foo {
def print(f: Foo) = println(s"Foo with ${f.i}")
}
// Different projects/components can define their own
// methods.
object FooP1 {
def mySpecificPrint(f: Foo) = println(s"Foo with ${f.i}")
}
object FooP2 {
def mySpecificPrint(f: Foo) = println(s"Foo with ${f.i}")
}
// Ok you have a method that have to access a lot of fields in
// Foo and it's annoying to type `f.` every time?
// remember in scala you can import instances.
object FooP3 {
def mySpecificPrint(f: Foo) = {
import f._
println(s"Foo with ${i}")
}
}
}
/** Yeah but I like to use the OO notation,
* I prefer `foo.print` to `Foo.print(foo)`
* No problem, we have implicit conversions
*/
object v3 {
case class Foo(i: Int)
class RichFoo(f: Foo) {
def print = println(s"Foo with ${f.i}")
}
implicit def rf(f: Foo): RichFoo = new RichFoo(f)
// This type of conversion is safe, if there are two valid implicits
// available the compiler will give an error,
// and intellij, if you click on the method, will bring you
// in the right place!
Foo(1).print
}
/** Event better, there is a special syntax for this now
*/
object v4 {
case class Foo(i: Int)
implicit class RichFoo(f: Foo) {
def print = println(s"Foo with ${f.i}")
}
Foo(1).print
}
/** Some library now is supporting both syntax
* using companion and implicit conversion together
*/
object v5 {
case class Foo(i: Int)
// the companion define the implementations
object Foo {
def print(f: Foo) = println(s"Foo with ${f.i}")
}
// the implicit class is just to add the syntax to the
// instances, often the syntax code lives in a separate package
implicit class FooSyntax(f: Foo) {
def print = Foo.print(f)
}
Foo(1).print
}
}
/**
* DON'T do that even if many libraries do it
*/
object dont {
case class Bar(s: String) {
def print = println(s"$s")
}
implicit def string2bar(s: String) = Bar(s)
def printBar(b: Bar): Unit = b.print
printBar("ciao")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment