Functions
Data Types
Much like ML (and Java/C# Generics)
Except that you have to write down the type variables
let id x = x
def id[A](x: A): A = x
Much like ML (and Java/C# Generics)
Except that you have to write down the type variables
val swap : ('a * 'b) -> ('b * 'a)
let swap p = (snd p, fst p)
def swap[A, B](p: (A, B)) : (B, A) = (p._2, p._1)
Much like ML (and Java/C# Generics)
Except that you have to write down the type variables
let rec length xs = match xs with
| [] -> 0
| _::t -> 1 + length t
def length[A](xs: List[A]): Int = xs match {
case Nil => 0
case (_ :: t) => 1 + length(t)
}
Much like ML (and Java/C# Generics)
Except that you have to write down the type variables
let rec map f xs = match xs with
| [] -> []
| x::t -> (f x) :: map (f, t)
def map[A, B](f: A => B)(xs: List[A]): List[B] = xs match {
case Nil => List()
case (_ :: t) => (f(x))::(map(f)(rest))
}
//Subtype Polymorphism
def headAny(xs: List[Any]): Any = xs match {
case h :: _ => h
}
//Parametric Polymorphism
def headPoly[A](xs: List[A]): A = xs match {
case h :: _ => h
}
A. Yes
B. No because headAny accepts more inputs
C. No because headPoly accepts more inputs
Functions
Data Types
Lets write a polymorphic library for representing (cough Sets!)
sealed abstract class Bag[A]
case class Empty[A]() extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A]) extends Bag[A]
The above is very similar to what you might write in Ocaml
type 'a bag = Empty | Plus of ('a * 'a bag)
Lets write a polymorphic library for representing (cough Sets!)
sealed abstract class Bag[A]
case class Empty[A]() extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A]) extends Bag[A]
new to create instancesLets write a polymorphic library for representing (cough Sets!)
sealed abstract class Bag[A]
case class Empty[A]() extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A]) extends Bag[A]
Lets write a polymorphic library for representing (cough Sets!)
We can fill in various methods...
sealed abstract class Bag[A] {
def size : Int =
this match {
case Empty() => 0
case Plus(_, rest) => 1 + rest.size
}
// ...
}
this to pattern match against the receiver bagLets write a polymorphic library for representing (cough Sets!)
We can fill in various methods...
sealed abstract class Bag[A] {
// ...
def contains(x: A) : Boolean = {
this match {
case Empty() => false
case Plus(e, _) if (x == e) => true
case Plus(_, rest) => rest.contains(x)
}
}
// ...
}
A is in scope in entire class definitionLets write a polymorphic library for representing (cough Sets!)
We can fill in various methods...
sealed abstract class Bag[A] {
// ...
def add(x: A) : Bag[A] = {
if (this.contains(x)) this else { Plus(x, this) }
}
// ...
}
Note Type parameter A is in scope in entire class definition
Bag returned as output...BagLets write a polymorphic library for representing (cough Sets!)
We can fill in various methods...
sealed abstract class Bag[A] {
// ...
def gimme: Option[A] =
this match {
case Plus(x, rest) => Some(x)
case _ => None
}
// ...
}
Note Type parameter A is in scope in entire class definition
Parametric Polymorphism ("type variables", ML style)
Subtyping + Parametric Polymorphism
Implicitly retrofitting behavior to existing types
def findMin[A](cur: A, xs: List[A]): A =
xs match {
case Nil => cur
case h::t if (h < cur) => findMin(h, t)
case _::t => findMin(cur, t)
}
val res = findMin(1000, List(20, 4, 1, 6))
A. Type Error in findMin(1000, ...)
B. Type Error in definition of findMin
C. Type Error in List(20,4,1,6)
D. 1
E. 20
Bag is a pretty lame data structurecontainsaddMust walk over entire bag to determine if elements are present.
Lets change contains to utilize ordering property:
sealed abstract class Bag[A] {
// ...
def contains(x: A) : Boolean = {
this match {
case Empty() => false
case Plus(e, _) if (x == e) => true
case Plus(e, _) if (x > e) => false
case Plus(_, rest) => rest.contains(x)
}
}
// ...
}
OrderedBag.scala:16: error: value > is not a member of type parameter A
case Plus(e, _) if (x > e) => false

OrderedBag.scala:16: error: value > is not a member of type parameter A
case Plus(e, _) if (x > e) => false
contains requires more properties from A...contains for every type A ...sealed abstract class Bag[A] {
def contains(x: A) : Boolean = {
this match {
case Plus(e, _) if (x > e) => false
}
}
}
OrderedBag.scala:16: error: value > is not a member of type parameter A
case Plus(e, _) if (x > e) => false

trait Ord[A] {
//Must Be Implemented
def cmp(that: A): Int
//Automatically derived from cmp
def ===(that: A): Boolean = (this cmp that) == 0
def <(that: A): Boolean = (this cmp that) < 0
def >(that: A): Boolean = (this cmp that) > 0
def <=(that: A): Boolean = (this cmp that) <= 0
def >=(that: A): Boolean = (this cmp that) <= 0
}
cmp ...sealed abstract class Bag[A <: Ord[A]] {
//...
}
Ord to State RequirementsA that is a subtype of Ord[A] (i.e. that implements Ord[A])Bag[A]A in containsNow we can go ahead and do comparisons on A
sealed abstract class Bag[A <: Ord[A]] {
// ...
def contains(x: A) : Boolean = {
this match {
case Empty() => false
case Plus(e, _) if (x == e) => true
case Plus(_, rest) => rest.contains(x)
}
}
// ...
}
add method so objects are put in the right positionsealed abstract class Bag[A <: Ord[A]] {
//...
def add(x: A) : Bag[A] =
this match {
case Plus(e, rest) if (x > e) => Plus(e, rest.add(x))
case Plus(e, _) if (x == e) => this
case _ => Plus(x, this)
}
//...
}
remove method to make it more efficientNo need to go to the end
sealed abstract class Bag[A <: Ord[A]] {
def remove(x: A) : Bag[A] = {
this match {
case Plus(e, rest) if (x < e) => this // no need to look further
case Empty() => this
case Plus(e, rest) if (x == e) => rest
case Plus(e, rest) => Plus(e, rest.remove(x))
}
}
}
Define function, class, etc. for any type A that is a subtype of something
sealed abstract class Bag[A <: Ord[A]] {
//...
}
A that satisfies a conditiondef findMin[A <: Ord[A]](cur: A, xs: List[A]): A =
xs match {
case Nil => cur
case h::t if (h < cur) => findMin(h, t)
case _::t => findMin(cur, t)
}
val res = findMin(1000, List(20, 4, 1, 6))
A. Type Error in findMin(1000, ...)
B. Type Error (in definition of findMin)
C. Type Error (in List(20, 4, 1, 6))
D. 1
E. 20
Using FixedOrderedBag.scala we get
$ make fobag
$ scala
scala> Bag(1,2,3)
<console>:8: error: inferred type arguments [Int] do not conform to
method apply's type parameter bounds [A <: Ord[A]]
Bag(1,2,3)
Using FixedOrderedBag.scala we get
$ make fobag
$ scala
scala> Bag(1,2,3)
<console>:8: error: inferred type arguments [Int] do not conform to
method apply's type parameter bounds [A <: Ord[A]]
Bag(1,2,3)
Ord ...Int does not implement Ord!!!Int but thats lame...Parametric Polymorphism ("type variables", ML style)
Subtyping + Parametric Polymorphism
Implicitly retrofitting behavior to existing types
We just made up a brand new trait Ord ...
... so of course the existing type Int does not implement Ord!
def findMin[A](cur: A, xs: List[A])(proxy: A => Ord[A]): A =
xs match {
case Nil => cur
case h::t if (proxy(h) < cur) => findMin(h, t)(proxy)
case _::t => findMin(cur, t)(proxy)
}
Int need not itself implement Ord[Int]
Int need not support comparisons with Intproxy(h) < cur instead of h < curdef findMin[A](cur: A, xs: List[A])(proxy: A => Ord[A]): A =
xs match {
case Nil => cur
case h::t if (proxy(h) < cur) => findMin(h, t)
case _::t => findMin(cur, t)
}
def intProxy(x: Int) = new Ord[Int] {
def cmp(that: Int) : Int = x - that
}
x: Int to an Ord[Int] supporting comparisons with xproxy(h) < cur will hold exactly when h is less than curdef findMin[A](cur: A, xs: List[A])(proxy: A => Ord[A]): A =
xs match {
case Nil => cur
case h::t if (proxy(h) < cur) => findMin(h, t)(proxy)
case _::t => findMin(cur, t)(proxy)
}
def intProxy(x: Int) = new Ord[Int] {
def cmp(that: Int) : Int = x - that
}
scala> import ProxyDemo._
scala> findMin(1000, List(20, 4, 1, 6))(intProxy)
res1: Int = 1
def stringProxy(x: String) = new Ord[String] {
def cmp(that: String) : Int = x compare that
}
scala> import ProxyDemo._
scala> findMin("z", List("apple", "chorizo", "machaca", "adobado"))(stringProxy)
res1: String = adobado
Int as implementation of Ord[Int]String as implementations of Ord[String]def sayHello(implicit n: String) = println("Hello " + n)
def sayHello(implicit n: String) = println("Hello " + n)
implicit val defaultName = "Ranjit"
def sayHello(implicit n: String) = println("Hello " + n)
implicit val defaultName = "Ranjit"
scala> sayHello("Roberto")
Hello Roberto
def sayHello(implicit n: String) = println("Hello " + n)
implicit val defaultName = "Ranjit"
scala> sayHello("Roberto")
Hello Roberto
scala> sayHello
Hello Ranjit
Scala uses types to fill in the blanks
TT value ...Suppose I wrote a function expecting String inputs
scala> def yu(msg: String) = println("Y U NO " + msg)
yu: (msg: String)Unit
Suppose I wrote a function expecting String inputs
scala> def yu(msg: String) = println("Y U NO " + msg)
yu: (msg: String)Unit
Naturally, this is fine ...
scala> yu("GIVE EASY FINAL")
Y U NO GIVE EASY FINAL
Suppose I wrote a function expecting String inputs
scala> def yu(msg: String) = println("Y U NO " + msg)
yu: (msg: String)Unit
Naturally, this is fine ...
scala> yu("GIVE EASY FINAL")
Y U NO GIVE EASY FINAL
... but this throws an error
scala> yu(10)
<console>:9: error: type mismatch;
found : Int(10)
required: String
yu(10)
^
Suppose I wrote a function expecting String inputs
scala> def yu(msg: String) = println("Y U NO " + msg)
yu: (msg: String)Unit
But if we add an implicit conversion
scala> implicit def i2s(i: Int) = i.toString
i2s: (i: Int)java.lang.String
Suppose I wrote a function expecting String inputs
scala> def yu(msg: String) = println("Y U NO " + msg)
yu: (msg: String)Unit
But if we add an implicit conversion
scala> implicit def i2s(i: Int) = i.toString
i2s: (i: Int)java.lang.String
Then lo and behold!
scala> yu(10)
Y U NO 10
yu(10) automatically fixed to yu(i2s(10))i2sProxies require we pass around functions & clutter code...
... but implicits automatically insert proxies where needed!
Step 1: Make proxy parameters implicit
def findMin[A](cur: A, xs: List[A])(implicit proxy: A => Ord[A]): A =
xs match {
case Nil => cur
case h::t if (proxy(h) < cur) => findMin(h, t)(proxy)
case _::t => findMin(cur, t)(proxy)
}
Step 2: Remove explicit uses of proxy (Scala automatically inserts them!)
def findMin[A](cur: A, xs: List[A])(implicit proxy: A => Ord[A]): A =
xs match {
case Nil => cur
case h::t if (/*proxy*/h < cur) => findMin(h, t)/* (proxy) */
case _::t => findMin(cur, t)/* (proxy) */
}
Step 2: Remove explicit uses of proxy (Scala automatically inserts them!)
def findMin[A](cur: A, xs: List[A])(implicit proxy: A => Ord[A]): A =
xs match {
case Nil => cur
case h::t if (h < cur) => findMin(h, t)
case _::t => findMin(cur, t)
}
Step 3: Make proxy functions implicit
implicit def intProxy(x: Int) = new Ord[Int] {
def cmp(that: Int) : Int = x - that
}
implicit def stringProxy(x: String) = new Ord[String] {
def cmp(that: String) : Int = x compare that
}
After steps
Make proxy parameters implicit
Remove explicit uses of proxy (Scala automatically inserts them!)
Make proxy functions implicit
After steps
Make proxy parameters implicit
Remove explicit uses of proxy (Scala automatically inserts them!)
Make proxy functions implicit
We get:
scala> val res = findMin(1000, List(10, 2, 30, 14)) // look ma, no proxies!
res: Int = 2
Instead of:
def findMin[A](cur: A, xs: List[A])(implicit proxy: A => Ord[A]): A
we can just write the equivalent
def findMin[A <% Ord[A]](cur: A, xs: List[A]): A
Instead of:
def findMin[A](cur: A, xs: List[A])(implicit proxy: A => Ord[A]): A
we can just write the equivalent
def findMin[A <% Ord[A]](cur: A, xs: List[A]): A
A <: Ord[A]
Means: For any A with an implicit proxy mapping A to Ord[A]
We can now make a real ordered bag
sealed abstract class Bag[A <% Ord[A]] {
def contains(x: A) : Boolean = {
this match {
case Empty() => false
case Plus(e, _) if (x == e) => true
case Plus(e, _) if (x > e) => false
case Plus(_, rest) => rest.contains(x)
}
}
// etc.
We can now make a real ordered bag
We make a companion object that makes it easy to create Bags
/* A Companion Object that has various key methods */
object Bag {
def apply[A <% Ord[A]](xs: A*) : Bag[A] = {
val s0 : Bag[A] = Empty()
xs.toList.foldLeft(s0)(_ add _)
}
}
Note: the view bound on apply
We can now make a real ordered bag
We make a companion object that makes it easy to create Bags
We can finally use Bag with existing types!
scala> val b0 = Bag(3,1,4,2)
<console>:7: error: No implicit view available from Int => Ord[Int].
val b0 = Bag(3,1,4,2)
^
First, lets import the proxies into scope ...
scala> import OrdInstances._
First, lets import the proxies into scope ...
scala> import OrdInstances._
And we're off...
scala> val b0 = Bag(3,1,2) // Now it can insert the intProxy
b0: Bag[Int] = 1,2,3,4 // Note how the Bag is ordered
scala> b0 gimme
res0: Option[Int] = Some(1)
scala> b0 remove 1 gimme
res1: Option[Int] = Some(2)
scala> b0 remove 1 remove 2 gimme
res2: Option[Int] = Some(3)
scala> b0 remove 1 remove 2 remove 3 gimme
res3: Option[Int] = None
We can use any type for which an implicit proxy is defined:
scala> val b1 = Bag("donkey", "monkey", "cat")
b1: Bag[java.lang.String] = cat,donkey,monkey
scala> b1 contains "monkey"
res: Boolean = true
scala> b1 contains "hippo"
res: Boolean = false
scala> val b2 = b1 add "hippo"
b2: Bag[java.lang.String] = cat,donkey,hippo,monkey
scala> b2 contains "hippo"
res: Boolean = true
b1 contains "hippo" return now?Scala will automatically generate new proxies (if we teach it how)
implicit def tup2Proxy[A <% Ord[A], B <% Ord[B]](x: (A, B))
= new Ord[(A, B)] {
def cmp(that: (A, B)) : Int = {
val c1 = x._1 cmp that._1
if (c1 != 0) c1 else { x._2 cmp that._2 }
}
}
Scala will automatically generate new proxies (if we teach it how)
implicit def tup2Proxy[A <% Ord[A], B <% Ord[B]](x: (A, B))
= new Ord[(A, B)] {
def cmp(that: (A, B)) : Int = {
val c1 = x._1 cmp that._1
if (c1 != 0) c1 else { x._2 cmp that._2 }
}
}
If you have an (implicit) proxies for type A and type B
Then you have an (implicit) proxy for type (A, B)
And now we can make Bags of all sorts of pairs (over Int and String)
scala> val tb = Bag((3, "cat"), (2, "horse"), (1, "zebra"), (0, "giraffe"))
tb: Bag[(Int, java.lang.String)] = (0,giraffe),(1,zebra),(2,horse),(3,cat)
scala> val tb = Bag((2, (1, "cat")), (30, (20, "horse")), (11, (7, "zebra")))
tb: Bag[(Int, (Int, java.lang.String))] = (2,(1,cat)),(11,(7,zebra)),(30,(20,horse))
List, Set, and MapParametric Polymorphism ("type variables", ML style)
def add[A](x: A): Bag[A]Subtyping + Parametric Polymorphism
def add[A <: Ord[A]](x: A): Bag[A]Implicitly retrofitting behavior to existing types
def add[A <% Ord[A]](x: A): Bag[A]