Lecture 08: Polymorphism

/*
  Tipuri de polimorfism
 
 
 */
// Overloading (in limbaje OO si Imperative in general)
// supraincarcare
 
// (1) Mai general, polimorfism ad-hoc
 
def toString(x: Int): String = ??? // impl 1
def toString(c: Char): String = ??? // impl 2
 
// Avem "un nume", "mai multe tipuri", "mai multe implementari"
 
 
class Animal {
  def sing: String = "La la la"
}
class Cat extends Animal{
  override def sing: String = "Miau"
}
 
def toString(a: Animal): String = ??? // impl 3
def toString(c: Cat): String = ??? // impl 4
 
val x: Animal = new Cat
toString(x) // compile-time. Impl 3 este cea chemata
 
// Overriding (depinde de existenta unei relatii de mostenire intre clase)
val l: List[Animal] = List (new Cat, new Animal)
val songs = for (a <- l) yield a.sing
// overriding - la runtime
// (2) Subtype polymorphism
 
// (3) Parametric polymorphism
// in Java = Genericitate (based on type-errasure).
 
/*
List l = ...
for (Object x: l)
  ((Animal)x).sing
*/
 
// a parametrically-polymorphic function
def size[A] (l:List[A]): Int = ???
// Avem "un nume", "mai multe tipuri", "o singura implementare"
 
def foldRight[A,B](l: List[A])(acc: B)(op:(A,B) => B): B = ???
 
/* Mai multe despre polimorfismul parametric
*
* Aplicatii: Colectii (Liste, stive, map-uri, perechi, arbori)
*
* Sa presupunem ca vrem sa introducem error-handling in programele noastre.
*
*  */
 
/*
// more coding work V
case object Error extends Nat{
  def +(other: Nat): Nat = ???
  def -(other: Nat): Nat = ???
}
*/
trait Nat {
  def +(other: Nat): Nat
  def +(other: Result[Nat]): Result[Nat]
  def -(other: Nat): Result[Nat]
}
 
trait Result[A] {
  def map[B](f: A => B): Result[B]
}
 
case class Value[A](v: A) extends Result[A] {
  override def map[B](f: A => B): Result[B] =
    Value(f(v))
}
 
//case object Error extends Result[A] // covarianta tipurilor (type variance)
case class Error[A](m: String) extends Result[A] {
  override def map[B](f: A => B): Result[B] =
    Error(m)
}
 
 
case object Zero extends Nat {
  override def +(other: Nat):Nat = other
  override def +(other: Result[Nat]): Result[Nat] = other
  override def -(other: Nat):Result[Nat] =
    other match {
      case Zero => Value(Zero)
      case _ => Error("Negative value")
    }
}
 
case class Succ(n: Nat) extends Nat {
  override def +(other: Nat): Nat = Succ(n + other)
  override def +(other: Result[Nat]): Result[Nat] =
    other match {
      case Error(msg) => other
      case Value(m) => Value(Succ(n + m))
    }
  override def -(other: Nat): Result[Nat] =
    other match {
      case Zero => Value(this)
      case Succ(y) => n - y
    }
}
 
def fromInt(i: Int): Result[Nat] = {
  if (i < 0) Error("Negative integer")
  else if (i == 0) Value(Zero)
  else fromInt(i-1) match {
    case Error(m) => Error(m)
    case Value(n) => Value(Succ(n))
  }
}
// Nat       Result[Nat]
Succ(Zero) + fromInt(1)
 
//Result[Nat]   Nat
// asta nu va merge: fromInt(1) + Succ(Zero)
 
fromInt(1) // Result[Nat]
  .map(_ + Succ(Zero))
  .map(_ + fromInt(1))
//  .map(_ - Succ(Zero))  trebuie sa implementam si -(other: Result[Nat]): Result[Nat]
 
 
/*
Despre Result!!! F.F. important
 
Result exista deja, se numeste "Option".
  - None (echivalentul lui Error)
  - Some (echivalentul lui value)
 
Un stil de scriere a programelor folosind Option
 */
 
def f[A](l: List[A]): Int = ???
 
/*
    La andThen - prima functie sa aiba signatura scrisa deja (cu lambda)
               - fiecare functie sa fie inchise in paranteze
               - toata constructia sa fie inchisa in paranteze
 */
 
val cutie = Some(List(1,2,3))
  cutie
    .map(_.map(_+1))
    .map(_.filter(_>0))
    .map(f)
    .map(_+1)  // comments
 
cutie match {
  case Some(v) => ??? // do something
}
 
f(List(1,2,3)
  .map(_+1)
  .filter(_>0)) + 1