trait Nat {
  def +(n: Nat): Nat
  def -(n: Nat): Nat  // difference over naturals, not integers
}
 
case object Zero extends Nat {
  override def + (n: Nat): Nat = n
  override def - (n: Nat): Nat = Zero
}
 
case class Succ(n: Nat) extends Nat {
  override def + (m: Nat): Nat = Succ(n + m)
  override def - (m: Nat): Nat =
    m match {
      case Zero => Succ(n)
      case Succ(mp) => n - mp
    }
}
 
/*
 Vreau sa iau o lista de intregi (Int) si sa o transform
 intr-o lista de Nat DAR e posibil ca unii Int sa fie negativi
 vreau sa il ignor.
 */
 
//trait Result[A] // Option
//case class Error[A](msg: String) extends Result[A] //None
//case class Value[A](value: A) extends Result[A] //Some
 
 
def toNat(i: Int): Option[Nat] = {
  def fromValidInt(i: Int): Nat =
    if (i == 0) Zero
    else Succ(fromValidInt(i-1))
  if (i < 0) None
  else Some(fromValidInt(i))
}
 
toNat(-1)
toNat(2)
 
def fromIntList(l: List[Int]): List[Nat] =
  l.map(toNat)
    .foldRight(Nil:List[Nat])((x, acc) =>
                                x match {
                                  case None => acc
                                  case Some(n) => n :: acc
                                })
 
val m: Map[Int,Int] = Map (1 -> 2, 3 -> 4)
 
1 -> 2 // pereche
1.->(2)  // pereche
(1,2) // same as above
 
if (m.contains(0)) m(0) else -1
 
m.get(0) match{ //Option[Int]
  case None => -1
  case Some(x) => x
}
 
m.withDefaultValue(-1)(0)
 
/* Covarianta tipurilor */
 
// A,B,C
// T,U,V
 
trait Tree[+A] {
  def size: Int
  def map[B](f: A => B):Tree[B]
  def flatten:List[A]
}
 
// case object Void[A]
case class Void[A]() extends Tree[A] {
  override def size: Int = 0
  override def map[B](f: A => B):Tree[B] =
    Void()
  override def flatten: List[A] = Nil
}
 
case class Node[A](left: Tree[A], key: A, right:Tree[A]) extends Tree[A] {
  override def size: Int = 1 + left.size + right.size
  override def map[B](f: A => B): Tree[B] =
    Node(left.map(f),f(key),right.map(f))
 
  override def flatten: List[A] = left.flatten ++ List(key) ++ right.flatten
}
 
class Animal {
  def sing: String = "An animal sings"
}
 
class Dog extends Animal {
  override def sing: String = "Wof!"
}
 
class Cat extends Animal {
  override def sing: String = "Miau"
}
 
 
val catTree = Node(Void(),new Cat, Void())
val animalTree = Node(Void(), new Cat, Node(Void(), new Dog, Void()))
 
val tree = Node(Void(), new Dog, catTree)
 
def singTree(t: Tree[Animal]): String =
  t.map(_.sing).flatten.reduce(_ + _)
 
// Serialiser[A <: Animal] echivalent cu Serialiser<A extends Animal>
class Serialiser[-A <: Animal] {
  def serialise(a: A): String = a.sing
}
 
val animalSerialiser = new Serialiser[Animal]
val catSerialiser: Serialiser[Cat] = animalSerialiser
 
/*
   Ar trebui ca un Serialiser[Animal] sa fie subtip al Serialiser[Cat]
    daca stiu sa serializez un Cat, atunci stiu sa serializez si un Animal
    DA!
 
    Ar trebui ca Serialiser[Cat] sa fie subtip al Serialiser[Animal] ?
    NU!
 */