Unlike OCaml, you have to write down the type variables:
/* OCaml: let id x = x */
scala> def id[A](x:A) = x
id: [A](x: A)A
/* OCaml: let swap p = (snd p, fst p) */
scala> def swap[A,B](p: (A,B)) = (p._2, p._1)
swap: [A, B](p: (A, B))(B, A)
'a -> 'a in OCaml really meant "forall 'a. 'a -> 'a"[...]At function call, can explicitly instantiate the type parameters:
scala> swap[Int, Boolean](1, true)
res10: (Boolean, Int) = (true,1)
But often, local type inference can implicitly instantiate them:
scala> swap(1, true)
res11: (Boolean, Int) = (true,1)
Recall the type ascription expression from last time.
scala> List()
res17: List[Nothing] = List()
scala> List(): List[Int]
res18: List[Int] = List()
scala> List(): List[Boolean]
res19: List[Boolean] = List()
scala> List(): List[List[Int]]
res20: List[List[Int]] = List()
Recall the type ascription expression from last time.
scala> Nil
res23: scala.collection.immutable.Nil.type = List()
scala> Nil: List[Int]
res24: List[Int] = List()
scala> Nil: List[Boolean]
res25: List[Boolean] = List()
scala> Nil: List[List[Int]]
res26: List[List[Int]] = List()
Wait, there are two ways to describe the empty list?
scala> Nil: List[Int]
res24: List[Int] = List()
scala> List(): List[Int]
res18: List[Int] = List()
scala> Nil == List()
res32: Boolean = true
Datatypes in OCaml were awesome.
# type t = A | B ;;
# let foo x = match x with A -> () ;;
Warning: this pattern-matching is not exhaustive.
foo : t -> unit
# let bar x = match x with A -> () | B -> () | B -> () ;;
Warning: this match case is unused.
bar : t -> unit
# type t = A | B ;;
foo, bar, baz) for every t-typed value.class t
class A() extends t
class B() extends t
A, B, C) of t-typed value.A mix of classes and datatypes...
class t
case class A() extends t
case class B() extends t
... that enables pattern matching!
def foo(x: t) = x match {
case A() => println("A")
case B() => println("B")
}
class t
case class A() extends t
def bar(x: t) = x match {
case A() => println("A")
case A() => println("A")
}
What is the result of evaluating bar?
A: Type Error
B: t -> Unit
C: t -> Unit with a warning
class t
case class A() extends t
def bar(x: t) = x match {
case A() => println("A")
case A() => println("A")
}
t -> Unit with a warningclass t
case class A() extends t
case class B() extends t
def baz(x: t) = x match {
case A() => println("A")
}
What is the result of evaluating bar?
A: Type Error
B: t -> Unit
C: t -> Unit with a warning
class t
case class A() extends t
case class B() extends t
def baz(x: t) = x match {
case A() => println("A")
}
t -> Unit (with no warning)If a (regular or case) class is marked as sealed...
sealed class t
case class A() extends t
case class B() extends t
def baz(x: t) = x match {
case A() => println("A")
}
(* ML *)
let rec length xs = match xs with
| [] -> 0
| _::t -> 1 + length t
/* Scala */
def length[A](xs: List[A]): Int =
xs match {
case Nil => 0
case (_ :: t) => 1 + length(t)
}
(* ML *)
let rec map f xs = match xs with
| [] -> []
| x::t -> (f x) :: map (f, t)
/* Scala */
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
case _ => sys.error("head of empty list")
}
// Parametric Polymorphism
def headPoly[A](xs: List[A]): A =
xs match {
case h :: _ => h
case _ => sys.error("head of empty list")
}
// Subtype Polymorphism
def headAny(xs: List[Any]): Any = ...
// Parametric Polymorphism
def headPoly[A](xs: List[A]): A = ...
headAny is very imprecise.headPoly is very precise.// Subtype Polymorphism
def headAny(xs: List[Any]): Any = ...
// Parametric Polymorphism
def headPoly[A](xs: List[A]): A = ...
scala> val ns = List(1,2,3)
ns: List[Int] = List(1, 2, 3)
scala> val (i, j) = (headAny(ns), headPoly(ns))
i: Any = 1
j: Int = 1
List[A].sealed abstract class Bag[A]
case class Empty[A]() extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A]) extends Bag[A]
type 'a bag = Empty | Plus of ('a * 'a bag)
sealed abstract class Bag[A]
case class Empty[A]() extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A]) extends Bag[A]
Case Classes are Just Vanilla Classes...
new to create instancessealed abstract class Bag[A]
case class Empty[A]() extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A]) extends Bag[A]

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 expression denotes the receiver bag.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 definition.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) }
}
// ...
}
Bag returned as output.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
}
// ...
}
this bag.res?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
res?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))
findMinerror: value < is not a member of type parameter A
case h::t if (h < cur) => findMin(h, t)
^
trait Ord[A] {
def cmp(that: A): Int // Must Be Implemented ...
// ... 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
}
def findMin[???](cur: ???, xs: List[???]): ??? =
xs match {
case Nil => cur
case h::t if (h < cur) => findMin(h, t)
case _::t => findMin(cur, t)
}
A... (parametric polymorphism)Ord. (subtyping)[A <: Ord[A]]def 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)
}
Bag.Bag is a pretty lame data structurecontains, add, etc.Bst in Homework 6...Bagsealed abstract class Bag[A <: Ord[A]] {
// ...
}
A that is a subtype of Ord[A] (i.e. that implements Ord[A])Bag[A]A faster version of contains that does not walk the entire 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)
}
// ...
}
... Assuming that elements are ordered in the first place!
So, we must change the add method to enforce this invariant.
sealed abstract class Bag[A <: Ord[A]] {
// ...
def add(x: A) : Bag[A] =
this match {
case Plus(e, es) if (x > e) => Plus(e, es.add(x))
case Plus(e, _) if (x == e) => this
case _ => Plus(x, this)
}
// ...
}
The remove method can also be made a bit more efficient.
No need to go to the end:
sealed abstract class Bag[A <: Ord[A]] {
// ...
def remove(x: A) : Bag[A] =
this match {
case Plus(e, es) if (x < e) => this
case Empty() => this
case Plus(e, es) if (x == e) => es
case Plus(e, es) => Plus(e, es.remove(x))
}
// ...
}
[A <: T] describes
A... (parametric polymorphism)T. (subtyping)res?def 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. 1
res?def 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 with type argument Int,Int is a subtype of Ord[Int].Ord...Int is not a subtype of Ord[Int]!res?findMin(1000, ...)scala> val res = findMin(1000, List(1,2,3))
error: inferred type arguments [Int] do not conform to
method findMin's type parameter bounds
[A <: Ord[A]]
scala> val res = findMin[Int](1000, List(1,2,3))
error: type arguments [Int] do not conform to
method findMin's type parameter bounds
[A <: Ord[A]]
We have studied many general themes in the context of
instanceOf in Scala or Java)null pointers, infinite loops)Remember these powerful building blocks when you:
findMin...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)
case _::t => findMin(cur, t)
}
Int need not itself implement Ord[Int]
Int need not support comparisons with Intdef findMin[A](cur: A, xs: List[A])(proxy: A => Ord[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] object supporting comparisons with xdef findMin[A](cur: A, xs: List[A])(proxy: A => Ord[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
Of course, we can create proxies for other types, too:
def stringProxy(x: String) = new Ord[String] {
def cmp(that: String) : Int = x compare that
}
scala> import ProxyDemo._
scala> findMin("zz", List("apple", "chorizo", "adobado"))
(stringProxy)
res1: String = "adobado"
Int as implementation of Ord[Int]String as implementation of Ord[String]// Mark certain function params as implicit
def sayHello(implicit n: String) = println("Hello " + n)
// Mark certain values as implicit
implicit val defaultName = "Ranjit"
scala> sayHello("Roberto")
Hello Roberto
// Mark certain function params as implicit
def sayHello(implicit n: String) = println("Hello " + n)
// Mark certain values as implicit
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
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
Then lo and behold!
scala> yu(10)
Y U NO 10
yu(10) fixed to yu(int2String(10))i2sStep 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)
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
}
Make proxy parameters implicit.
Remove explicit uses of proxy. (Scala automatically inserts them!)
Make proxy functions implicit.
scala> val res = // look ma, no proxies!
findMin(1000, List(10, 2, 30, 14))
res: Int = 2
This pattern is so common that it warrants special syntax.
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]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.
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 (subtyping + parametric) polymorphism on apply
We can finally use Bag with existing types!
scala> val b0 = Bag(3,1,4,2)
error: No implicit view available from Int => Ord[Int].
val b0 = Bag(3,1,4,2)
^
scala> import OrdInstances._
scala> val b0 = Bag(3,1,2) // Now it can insert intProxy
b0: Bag[Int] = 1,2,3,4 // Note: 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
Scala will automatically generate new proxies if we ask nicely:
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 }
}
}
A and type B(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]How does cons work?
scala> 1 :: 2 :: 3 :: Nil
res27: List[Int] = List(1, 2, 3)
:: is a method!1.::(2.::(3.::(Nil))) ?Methods that end in : (like cons ) are associated from right-to-left.
scala> 1.::(2.::(3.::(Nil)))
res33: List[Double] = List(1.0, 2.0, 3.0)
scala> Nil.::(3.).::(2.).::(1.)
res2: List[Double] = List(1.0, 2.0, 3.0)
scala> Nil.::(3).::(2).::(1)
res0: List[Int] = List(1, 2, 3)