Created
March 30, 2012 13:27
-
-
Save ppurang/2251556 to your computer and use it in GitHub Desktop.
bedcon 2012 presentation script
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
:silent | |
//the following are helper functions | |
def p = if(readLine() == "q") sys.exit | |
def pp[A]: ( => A) => Unit = a => {readLine() ; a} | |
object > { | |
def > : String => Unit = println _ | |
def >[A] : (String, => A) => Unit = (a,b) => {print("> " + a); readLine(); readLine(); println(" ----> " + b); println} | |
} | |
//the real demo starts here | |
> > """ | |
Welcome! Type Classes in Scala | |
""" | |
p | |
> > """ | |
Part 1 Motivation | |
===================================== | |
""" | |
p | |
//Scala does the right thing == just uses equals (in Java == is object identity) | |
> > ("1 == 1", 1 == 1) | |
p | |
> > ("1 == 2", 1 == 2) | |
p | |
> > """ | |
What about 1 == "1"? | |
""" | |
p | |
> > ("""1 == "1" """, 1 == "1") | |
//see - it even gives a warning. Now how do we correct this anomaly? | |
p | |
> > """ | |
Our goal 1 === 1 | |
===================================== | |
""" | |
p | |
> > ("""1 === 1""", 1 === "1") | |
p | |
> > """ | |
Part 2 Equality as a concept | |
===================================== | |
""" | |
p | |
> > """ | |
> trait Equality[A] { | |
> def equal(a: A, b: A) : Boolean | |
> } | |
""" | |
trait Equality[A] { | |
def equal(a: A, b: A) : Boolean | |
} | |
p | |
> > """ | |
> class IntEquality extends Equality[Int] { | |
> def equal(a: Int, b: Int) = a == b | |
> } | |
""" | |
class IntEquality extends Equality[Int] { | |
def equal(a: Int, b: Int) = a == b | |
} | |
p | |
> > """ | |
> val intEquality = new IntEquality | |
""" | |
val intEquality = new IntEquality | |
p | |
> > ("intEquality.equal(1,1)", intEquality.equal(1, 1)) | |
> > ("intEquality.equal(1,2)", intEquality.equal(1, 2)) | |
p | |
> > """> intEquality.equal(1, "1")""" | |
p | |
p | |
pp(intEquality.equal(1, "1")) | |
p | |
> > """ | |
Part 2.1 [refactor] to an object | |
===================================== | |
""" | |
p | |
> > """ | |
Instead of | |
> class IntEquality extends Equality[Int] { | |
> def equal(a: Int, b: Int) = a == b | |
> } | |
use a singleton | |
> object IntEquality extends Equality[Int] { | |
> def equal(a: Int, b: Int) = a == b | |
> } | |
""" | |
object IntEquality extends Equality[Int] { | |
def equal(a: Int, b: Int) = a == b | |
} | |
p | |
> > "" // some space because of the warning | |
> > ("IntEquality.equal(1,1)", IntEquality.equal(1,1)) | |
> > ("IntEquality.equal(1,2)", IntEquality.equal(1,2)) | |
p | |
> > """ | |
Part 2.2 [refactor] to a curried functional object | |
===================================== | |
""" | |
> > "> type Equality[A] = A => A => Boolean" | |
type Equality[A] = A => A => Boolean | |
p | |
p | |
> > """ | |
> object IntEquality extends Equality[Int] { | |
> def apply(a: Int) = b => a == b | |
> } | |
""" | |
p | |
object IntEquality extends Equality[Int] { | |
def apply(a: Int) = b => a == b | |
} | |
p | |
> > """ | |
> val isEqualToOne = IntEquality(1) | |
same as | |
> val isEqualToOne = IntEquality.apply(1) | |
""" | |
p | |
val isEqualToOne = IntEquality(1) | |
> > ("isEqualToOne(2)", isEqualToOne(2)) | |
> > ("IntEquality(1)(1)", IntEquality(1)(1)) | |
p | |
> > ("IntEquality.apply(1).apply(1)", IntEquality.apply(1).apply(1)) | |
p | |
> > """ | |
> val IntEquality2 : Equality[Int] = a => b => a == b | |
> val StringEquality : Equality[String] = a => b => a == b | |
""" | |
p | |
val IntEquality2 : Equality[Int] = a => b => a == b | |
val StringEquality : Equality[String] = a => b => a == b | |
> > ("IntEquality2 (1) (2)", IntEquality2(1)(2)) | |
> > ("""StringEquality("1")("2")""", StringEquality("1")("2")) | |
p | |
> > """ | |
Part 2.3 bump any == to a type safe equals | |
===================================== | |
""" | |
p | |
> > """ | |
> def EqualityForTheMasses[A] : Equality[A] = a => b => a == b | |
> val ListEquality = EqualityForTheMasses[ List[ _ ] ] | |
""" | |
p | |
def EqualityForTheMasses[A] : Equality[A] = a => b => a == b | |
val ListEquality = EqualityForTheMasses[ List[ _ ] ] | |
p | |
> > ("ListEquality( List(1,2,3) )( 1 :: 2 :: 3 :: Nil )", ListEquality(List(1,2,3))(1 :: 2 :: 3 :: Nil)) | |
> > ("ListEquality( List(1,2,3) )( 1 :: 2 :: 3 :: 4:: Nil )", ListEquality(List(1,2,3))(1 :: 2 :: 3 :: 4:: Nil)) | |
> > ("ListEquality( List() )( Nil )", ListEquality(List())(Nil)) | |
p | |
> > """ | |
yeah but what about | |
> ListEquality( List(1,2,3) )( Vector(1,2,3) ) //The vector is not comparable to Java Vector | |
""" | |
p | |
> > ("ListEquality( List(1,2,3) )( Vector(1,2,3) )", ListEquality(List(1,2,3))(Vector(1,2,3))) | |
p | |
> > """ | |
Hint lists and vectors are sequences | |
""" | |
p | |
> > """ | |
> val SeqEquality = EqualityForTheMasses[Seq[_]] | |
OR | |
> val SeqEquality = EqualityForTheMasses[ Seq[ _ ] ] | |
""" | |
p | |
val SeqEquality = EqualityForTheMasses[Seq[_]] | |
> > ("SeqEquality( List(1,2,3) )( Vector(1,2,3) )", SeqEquality(List(1,2,3))(Vector(1,2,3))) | |
> > ("SeqEquality( Vector(1,2,3,4) )( List(1,2,3) )", SeqEquality(Vector(1,2,3,4))(List(1,2,3))) | |
p | |
> > """ | |
!!! Wow !!! | |
""" | |
p | |
> > """ | |
Part 3 From a Function to a Method | |
===================================== | |
List(1,2,3) === List(1,2,3) | |
""" | |
p | |
> > """ | |
> trait TypeSafeEquality[A] { | |
> | |
> def a: A | |
> | |
> def ===(b: A) = a == b | |
> | |
> } | |
""" | |
p | |
> > """ | |
> trait TypeSafeEquality[A] { | |
> | |
> def a: A <----------------------- abstract | |
> | |
> def ===(b: A) = a == b <---------- default implementation | |
> | |
> } | |
""" | |
p | |
trait TypeSafeEquality[A] { | |
def a: A | |
def ===(b: A) = a == b | |
} | |
> > """ | |
> def intToTypeSafeEquality( anA: Int ) = new TypeSafeEquality[Int]{ def a: Int = anA } | |
""" | |
p | |
def intToTypeSafeEquality(anA: Int) = new TypeSafeEquality[Int]{def a: Int = anA} | |
p | |
> > ("intToTypeSafeEquality(1) === 1", intToTypeSafeEquality(1) === 1 ) | |
> > ("intToTypeSafeEquality(1) === 2", intToTypeSafeEquality(1) === 2 ) | |
p | |
> > ("intToTypeSafeEquality(1).===(2)", intToTypeSafeEquality(1).===(2)) | |
p | |
> > """ | |
What about | |
> 2.0 === 2.0 | |
""" | |
p | |
2.0 === 2.0 | |
p | |
p | |
> > """ | |
Part 3.1 Implicit conversion to a trait | |
===================================== | |
We need a better conversion to TypeSafeEquality that can take any type | |
""" | |
p | |
> > """ | |
> def anAToTypeSafeEquality[A](anA: A) : TypeSafeEquality[A] = new TypeSafeEquality[A]{def a = anA} | |
-----------^---------- | |
Optional | |
""" | |
p | |
def anAToTypeSafeEquality[A](anA: A) : TypeSafeEquality[A] = new TypeSafeEquality[A]{def a = anA} | |
> > """ | |
Let us define a new, rather simple, class | |
> class NoGoodEquals(val i: Int) | |
^ | |
don't forget the 'i' it is like a public final Integer in Java | |
""" | |
p | |
class NoGoodEquals(val i: Int) | |
p | |
> > """ | |
What about | |
> new NoGoodEquals(1) === new NoGoodEquals(1) | |
""" | |
p | |
new NoGoodEquals(1) === new NoGoodEquals(1) | |
p | |
p | |
> > ("anAToTypeSafeEquality(new NoGoodEquals(1)) === new NoGoodEquals(1)", anAToTypeSafeEquality(new NoGoodEquals(1)) === new NoGoodEquals(1) ) | |
p | |
> > """ | |
False is okay because class inherits Object's equals, which only treats identity as equality | |
but | |
we shouldn't need explicit conversion to TypeSafeEquality .. boilerplate! | |
""" | |
p | |
> > """ | |
> implicit def anAToTypeSafeEquality[A](anA: A) = new TypeSafeEquality[A]{def a = anA} | |
---^---- | |
""" | |
p | |
implicit def anAToTypeSafeEquality[A](anA: A) = new TypeSafeEquality[A]{def a = anA} | |
> > ("new NoGoodEquals(1) === new NoGoodEquals(1)", new NoGoodEquals(1) === new NoGoodEquals(1)) | |
p | |
p | |
> > """ | |
!!! boilerplate's gone but we are stuck with == which as we see is not good for all classes | |
""" | |
p | |
p | |
> > """ | |
Part 4 From an Okay Method to a Better Method | |
OR | |
How to improve the behavior of any given class with respect to equality without touching it | |
===================================== | |
""" | |
p | |
> > """ | |
> trait TypeSafeEquality[A] { | |
> | |
> def a: A | |
> | |
> def ===(b: A)(equal: Equality[A]) = equal(a)(b) | |
> } | |
//the following is needed again because we have changed the defn of TypeSafeEquality | |
//so the anAToTypeSafeEquality should rebind to the newer defn | |
//(we are in the same repl session) | |
> implicit def anAToTypeSafeEquality[A](anA: A) : TypeSafeEquality[A] = new TypeSafeEquality[A]{def a = anA} | |
""" | |
p | |
trait TypeSafeEquality[A] { | |
def a: A | |
def ===(b: A)(equal: Equality[A]) = equal(a)(b) | |
} | |
implicit def anAToTypeSafeEquality[A](anA: A) : TypeSafeEquality[A] = new TypeSafeEquality[A]{def a = anA} | |
> > """ | |
new NoGoodEquals(1) === new NoGoodEquals(1) | |
""" | |
p | |
new NoGoodEquals(1) === new NoGoodEquals(1) | |
p | |
> > """ | |
Oh no we broke it | |
what was that about things getting worse before.. | |
[Note] Scala compiler gives good warnings and good error reports | |
""" | |
p | |
> > ("(new NoGoodEquals(1)).===(new NoGoodEquals(1))(a => b => a.i == b.i)", (new NoGoodEquals(1)).===(new NoGoodEquals(1))(a => b => a.i == b.i)) | |
> > """ | |
hmmm that is so full of baloney | |
What did Feynman say about baloney? | |
""" | |
p | |
> > """ | |
Part 4.1 implicits again | |
aka | |
Simplify! | |
===================================== | |
""" | |
p | |
> > """ | |
> trait TypeSafeEquality[A] { | |
> def a: A | |
> | |
> def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
> ----^----- | |
> | |
> } | |
> implicit def anAToTypeSafeEquality[A](anA: A) : TypeSafeEquality[A] = new TypeSafeEquality[A]{def a = anA} | |
""" | |
p | |
trait TypeSafeEquality[A] { | |
def a: A | |
def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
} | |
implicit def anAToTypeSafeEquality[A](anA: A) : TypeSafeEquality[A] = new TypeSafeEquality[A]{def a = anA} | |
p | |
> > """ What about | |
> 1 === 1 | |
Wanna bet? | |
""" | |
p | |
p | |
> > ("1 === 1", 1 === 1) | |
p | |
> > """ | |
worse than we thought! | |
ha! we didn't define our equality constructs in section 2 above as implicits. | |
""" | |
p | |
> > """ | |
> implicit val intEquality = EqualityForTheMasses[Int] | |
""" | |
p | |
implicit val intEquality = EqualityForTheMasses[Int] | |
p | |
> > ("1 === 1", 1 === 1) | |
p | |
> > """ | |
phew! | |
so let us go GLOBAL for any type A i.e recreat EqualityForTheMasses | |
> implicit def EqualityForAll[A] : Equality[A] = a => b => a == b | |
""" | |
implicit def EqualityForAll[A] : Equality[A] = a => b => a == b | |
p | |
> > ("List(1,2,3) === 1 :: 2 :: 3 :: Nil", List(1,2,3) === 1 :: 2 :: 3 :: Nil) | |
p | |
> > ("List(1,2,3) === Vector(1,2,3)", List(1,2,3) === Vector(1,2,3)) | |
p | |
> > "and the hint was?" | |
p | |
p | |
> > ("(List(1,2,3) : Seq[Int]) === (Vector(1,2,3) : Seq[Int])", (List(1,2,3):Seq[Int]) === (Vector(1,2,3):Seq[Int])) | |
p | |
> > ("""Option("5") === Option(null)""", Option("5") === Option(null)) | |
> > ("""Option(null) === None""", Option(null) === None) | |
> > ("""Option("5") === Some("5")""", Option("5") === Some("5")) | |
p | |
> > ("""Right(5) === Right(5)""", Right(5) === Right(5)) | |
> > ("""Right(4) === Right(5)""", Right(4) === Right(5)) | |
p | |
> > """ | |
wow that was massive | |
EqualityForAll should cover 90% of the cases | |
""" | |
p | |
> > ("""new NoGoodEquals(1) === new NoGoodEquals(1)""", new NoGoodEquals(1) === new NoGoodEquals(1)) | |
p | |
> > """ | |
yes we can do better | |
> implicit val NoGoodEqualsVeryGoodEquality : Equality[NoGoodEquals] = a => b => a.i == b.i | |
-----------^---------- | |
Not optional | |
Why isn't it Optional? | |
""" | |
p | |
> > """ | |
Structural typing | |
No examples here... Back to business | |
""" | |
p | |
//yes we can do better | |
implicit val NoGoodEqualsVeryGoodEquality : Equality[NoGoodEquals] = a => b => a.i == b.i | |
p | |
> > ("new NoGoodEquals(1) === new NoGoodEquals(1)", new NoGoodEquals(1) === new NoGoodEquals(1)) | |
p | |
> > """ | |
@@@@@ Detour @@@ | |
Note: Scala provides a construct called a case class that does the right thing (equals. hashcode, toString etc) | |
and stuff not even there in Java like Extractors so let us just do the right thing | |
case class GoodEqualsByIntent(i: Int, j: Double) | |
""" | |
p | |
case class GoodEqualsByIntent(i: Int, j: Double) | |
p | |
> > ("GoodEqualsByIntent(1, 2.0) === GoodEqualsByIntent(1, 2.0)", GoodEqualsByIntent(1, 2.0) === GoodEqualsByIntent(1, 2.0)) | |
p | |
> > ("GoodEqualsByIntent(1, 2.1) === GoodEqualsByIntent(1, 2.0)", GoodEqualsByIntent(1, 2.1) === GoodEqualsByIntent(1, 2.0)) | |
p | |
> > """ | |
Works even though we didn't redefine == for GoodEqualsByIntent, because it is good ;) | |
""" | |
p | |
> > """ | |
SUCCESS we have what we need | |
1. Type safe | |
2. Terse | |
3. Does the right thing in most of the cases .. well behaved class => well behaved equality | |
4. Flexible for us to help the needy classes like NoGoodEquals | |
5. With a minimum of fuss we can change the default behaviour! | |
BUT wait things can get even better | |
""" | |
p | |
> > """ | |
5. Scoping | |
===================================== | |
> { //scoping | |
> | |
> //a universe with other rules... only the sign matters | |
> implicit val SignumEquality : Equality[Int] = a => b => scala.math.signum(a) == scala.math.signum(b) | |
> | |
> //... some statements here | |
> } | |
""" | |
p | |
p | |
{ //scoping | |
//a universe with other rules only the sign matters | |
implicit val SignumEquality : Equality[Int] = a => b => scala.math.signum(a) == scala.math.signum(b) | |
> >("1 === 1",1 === 1) | |
> >("-1 === -1", -1 === -1) | |
> >("1 === 2", 1 === 2) | |
> >("-1 === -2", -1 === -2) | |
> >("1 === -2", 1 === -2) | |
} | |
p | |
p | |
> > """ | |
6. Not equals, anyone? | |
===================================== | |
""" | |
p | |
> > """ | |
> trait TypeSafeEquality[A] { | |
> def a: A | |
> | |
> def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
> | |
> def =/=(b: A)(implicit equal: Equality[A]) = ! equal(a)(b) | |
> } | |
//alternative defn | |
> trait TypeSafeEquality[A] { | |
> | |
> self => | |
> | |
> def a: A | |
> | |
> def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
> | |
> def =/=(b: A)(implicit equal: Equality[A]) = ! (self === b) | |
> } | |
""" | |
p | |
p | |
> > """" | |
Who doesn't like =/=? | |
Alternatives !===, /== | |
""" | |
p | |
p | |
trait TypeSafeEquality[A] { | |
def a: A | |
def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
def =/=(b: A)(implicit equal: Equality[A]) = ! equal(a)(b) //scalaz uses /== | |
} | |
/* | |
//alternative defn | |
trait TypeSafeEquality[A] { | |
//lucky number slevin .. or was that seven :) | |
self => | |
def a: A | |
def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
def =/=(b: A)(implicit equal: Equality[A]) = ! (self === b) | |
}*/ | |
implicit def anAToTypeSafeEquality[A](anA: A) : TypeSafeEquality[A] = new TypeSafeEquality[A]{def a = anA} | |
> > """ Let us revisit that alternate Universe again... scope us out...""" | |
{ //scoping | |
//a universe with other rules only the sign matters | |
implicit val SignumEquality : Equality[Int] = a => b => scala.math.signum(a) == scala.math.signum(b) | |
> >("1 === 1",1 === 1) | |
> >("-1 === -2", -1 === -2) | |
> >("1 =/= -2", 1 =/= -2) | |
} | |
p | |
p | |
> > """ | |
7. Recap | |
===================================== | |
Declare a module: | |
> object Typesafe { | |
> | |
> // INTENT/CONCEPT | |
> type Equality[A] = A => A => Boolean | |
> | |
> // An Implementation | |
> val IntEquality : Equality[Int] = a => b => a == b | |
> | |
> // or even better if we can extend this intention to many types by | |
> //reusing their exising (even if sometimes faulty) implementation | |
> implicit def EqualityForAll[A] : Equality[A] = a => b => a == b | |
> | |
> // then we need a way to push the stuff as members of the various types | |
> // without touching them .. oooh AOP? open closed principle? implementation injection? | |
> trait TypeSafeEquality[A] { | |
> def a: A | |
> def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
> def =/=(b: A)(implicit equal: Equality[A]) = ! equal(a)(b) //scalaz uses /== | |
> } | |
> | |
> // allow some magic.. implicito! | |
> implicit def anAToTypeSafeEquality[A](anA: A) = new TypeSafeEquality[A]{def a = anA} | |
>} | |
""" | |
object Typesafe { | |
// so we need the intent also called a concept | |
type Equality[A] = A => A => Boolean | |
// we need an implementation | |
val IntEquality : Equality[Int] = a => b => a == b | |
// or even better if we can extend this | |
// intention to many types by reusing their exising (even if sometimes faulty) implementation | |
implicit def EqualityForAll[A] : Equality[A] = a => b => a == b | |
// then we need a way to push the stuff as members of the various types | |
// without touching them .. oooh AOP? open closed principle? implementation injection? | |
trait TypeSafeEquality[A] { | |
def a: A | |
def ===(b: A)(implicit equal: Equality[A]) = equal(a)(b) | |
def =/=(b: A)(implicit equal: Equality[A]) = ! equal(a)(b) //scalaz uses /== | |
} | |
// allow some magic | |
implicit def anAToTypeSafeEquality[A](anA: A) = new TypeSafeEquality[A]{def a = anA} | |
} | |
p | |
p | |
> > """ | |
How do we use it? | |
> object Recap { | |
> | |
> import Typesafe._ | |
> | |
> println { | |
> assert("Berlin" === "Berlin") | |
> assert(42 === 42) | |
> assert(List(1, "2", Option(3)) === List(1, "2", Option(3))) | |
> assert(List(1, "2", Option(3)) === List(1, "2", Option(4))) | |
> "Yes!" | |
> } | |
> } | |
""" | |
object Recap { | |
import Typesafe._ | |
println { | |
assert("Berlin" === "Berlin", "Berlin =/= Berlin") | |
assert(42 === 42, "42 =/= 42") | |
assert(List(1, "2", Option(3)) === List(1, "2", Option(3)), """List(1, "2", Option(3)) =/= List(1, "2", Option(3))""") | |
assert(List(1, "2", Option(3)) =/= List(1, "2", Option(4)), """List(1, "2", Option(3)) === List(1, "2", Option(4))""") | |
"Yes!" | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Transfer the contents to a file let us say 'TCBedCon2012.scala'.
Start the Scala console in the directory where the file is stored and once it initialises type the following
or
After that just use the 'enter' key to navigate and try and be a compiler/interpreter and come up with the answers.