package scalaz

trait Paramorphism[P[_]] {
  def para[A, B](fa: P[A], b: B, f: (=> A, => P[A], B) => B): B
}

object Paramorphism {
  implicit def ListParamorphism = new Paramorphism[List] {
    override def para[A, B](as: List[A], b: B, f: ((=> A, => List[A], B) => B)): B = as match {
      case Nil => b
      case a :: as => f(a, as, para(as, b, f))
    }
  }

  implicit def StreamParamorphism = new Paramorphism[Stream] {
    override def para[A, B](as: Stream[A], b: B, f: ((=> A, => Stream[A], B) => B)): B =
      if(as.isEmpty)
        b
      else
        f(as.head, as.tail, para(as.tail, b, f))
  }

  implicit def OptionParamorphism = new Paramorphism[Option] {
    def para[A, B](as: Option[A], b: B, f: ((=> A, => Option[A], B) => B)): B = as match {
      case None => b
      case Some(a) => f(a, None, b)
    }
  }
}