/*
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