Last active
October 21, 2016 11:09
-
-
Save gigiigig/1a6925c0bd2b5529a315 to your computer and use it in GitHub Desktop.
This file contains 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
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