package scalaz

trait Distributes[F[_], G[_]] {
  def apply[A](f: F[G[A]]): G[F[A]]
}

/** A natural transformation between functors F and G */
trait ~>[F[_], G[_]] {
  def apply[A](f: F[A]): G[A]
}

/** A constrained natural transformation */
trait Constrained[F[_], G[_], E[_]] {
  def apply[A: E](f: F[A]): G[A]
}

/** A transformation natural in both sides of a bifunctor */
trait ~~>[F[_,_], G[_,_]] {
  def apply[A,B](f: F[A,B]): G[A,B]
}

/** A constrained transformation natural in both sides of a bifunctor */
trait Biconstrained[F[_,_], G[_,_], C[_], E[_]] {
  def apply[A: C, B: E](f: F[A,B]): G[A,B]
}

trait Dinatural[F[_,_], G[_,_]] {
  def apply[A](f: F[A,A]): G[A,A]
}

trait Biff[P[_,_], F[_], G[_]] {
  type Apply[A, B] = P[F[A], G[B]]
}

trait On[P[_,_], F[_]] { 
  type Apply[A, B] = P[F[A], F[B]]
}

trait Extras {
  type Id[A] = A
  trait Konst[A] {
    type Apply[B] = A
  }
  type Thunk[A] = () => A

  /** A function type encoded as a natural transformation */
  type ->[A, B] = Konst[A]#Apply ~> Konst[B]#Apply

  /** A universally quantified identity function */
  def id = new (Id ~> Id) {
    def apply[A](a: A) = a
  }

  implicit def natToFunction[F[_], G[_], A](f: F ~> G): F[A] => G[A] = x => f(x)
}