package applicative

import scala.language.higherKinds

trait Applicative[F[_]] {
  def unit[A](a: A): F[A]

  def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = map2(ff, fa)((f, a) => f(a))

  // implement map using unit and ap, but not flatMap/flatten
  def map[A, B](fa: F[A])(f: A => B): F[B] = ???

  // implement map2 using unit and ap
  // hint: f.curried converts `(A,B) => C` to `A => (B => C)`
  def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = ???


  // implement unit first
  // then write the signature for ap and map2
  // then implement one of them
  def compose[G[_]](implicit G: Applicative[G]): Applicative[Lambda[a => F[G[a]]]] = {
    val F = this
    new Applicative[Lambda[a => F[G[a]]]] {

      def unit[A](a: A): F[G[A]] = ???
      //note: when you are done implementing this, the test for ap/map2 will result in a StackOverflow until you implement one of them


      // override def ap... = ???
      // or
      // override def map2... = ???
    }
  }
}