Skip to content
Snippets Groups Projects
Commit 145200df authored by Alexander Gehrke's avatar Alexander Gehrke
Browse files

[lec08] Add exercises for applicatives

parent 7d698f12
No related branches found
No related tags found
No related merge requests found
......@@ -17,6 +17,8 @@ usually marked with `???`.
| 4: Laziness | [`laziness`](src/main/scala/laziness/) | `testOnly laziness.*`
| 5: Algebras, Laws, and Monoids | [`algebra`](src/main/scala/algebra/) | `testOnly algebra.*`
| 6: Typeclasses | [`typeclasses`](src/main/scala/typeclasses/) | `testOnly typeclasses.*` (needs some uncommenting)
| 7: Monads | [`monads`](src/main/scala/monads/) | `testOnly monads.*` (needs some uncommenting)
| 8: Applicative Functors | [`applicative`](src/main/scala/applicative/) | `testOnly applicative.*` (needs some uncommenting)
## Usage tips:
To keep your local solutions to the exercises when pulling from the repository,
......
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... = ???
}
}
}
package applicative
import scala.language.higherKinds
trait Monad[F[_]] extends Applicative[F] {
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = flatten(map(fa)(f))
def flatten[A](fa: F[F[A]]): F[A] = flatMap(fa)(identity)
override def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(f andThen unit)
override def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = flatMap(fa)(a => map(fb)(b => f(a,b)))
}
package applicative
sealed trait Validated[+E, +A]
case class Valid[+A](a:A) extends Validated[Nothing, A]
case class Invalid[+E](head: E, tail: List[E] = List()) extends Validated[E, Nothing]
object Validated {
implicit def validatedApplicative[E]: Applicative[Validated[E, +?]] = new Applicative[Validated[E,+?]] {
def unit[A](a: A) = Valid(a)
// add map2 or ap here
}
}
package applicative
import org.scalatest._
import testutil.PendingIfUnimplemented
class ApplicativeSpec extends FlatSpec with Matchers with AppendedClues with PendingIfUnimplemented {
implicit val optionApplicative = new Applicative[Option] {
override def unit[A](a: A): Option[A] = Some(a)
override def ap[A, B](ff: Option[A => B])(fa: Option[A]): Option[B] = ff.flatMap(f => fa.map(f))
}
implicit val listApplicative = new Applicative[List] {
override def unit[A](a: A): List[A] = List(a)
override def ap[A, B](ff: List[A => B])(fa: List[A]): List[B] = ff.flatMap(f => fa.map(f))
}
"map" should "work the same when implemented via ap" in {
val f: Int => Int = i => i * 3
val g: String => String = s => "Hello " + s
val h: Int => String = _.toHexString
val someint = Some(2)
val somestring = Some("world")
optionApplicative.map(someint)(f) shouldBe someint.map(f)
optionApplicative.map(somestring)(g) shouldBe somestring.map(g)
optionApplicative.map(None)(g) shouldBe None.map(g)
optionApplicative.map(None)(g) shouldBe None.map(g)
}
"map2" should "combine two monadic values" in {
optionApplicative.map2(Some(1), Some(2))((a, b) => a + b) shouldBe Some(3)
optionApplicative.map2(Option.empty[Int], Some(2))((a, b) => a + b) shouldBe None
optionApplicative.map2(Some(2), Option.empty[Int])((a, b) => a + b) shouldBe None
}
"compose" should "create nested element with unit" in {
val ola: Applicative[Lambda[a => Option[List[a]]]] = optionApplicative.compose(listApplicative)
ola.unit(5) shouldBe Some(List(5))
}
it should "implement map2 or ap correctly" in {
val ola: Applicative[Lambda[a => Option[List[a]]]] = optionApplicative.compose(listApplicative)
val optList = Some(List(1, 2, 3, 4))
ola.map(optList)(_ + 10) shouldBe Some(List(11, 12, 13, 14))
}
}
package applicative
import org.scalatest._
import testutil.PendingIfUnimplemented
class ValidatedSpec extends FlatSpec with Matchers with AppendedClues with PendingIfUnimplemented {
"map2" should "accumulate errors" in {
type VSI = Validated[String, Int]
val valid1: VSI = Valid(1)
val valid2: VSI = Valid(2)
val invalidA: VSI = Invalid("A")
val invalidB: VSI = Invalid("B")
val invalidAB: VSI = Invalid("A", List("B"))
val invalidABAB: VSI = Invalid("A", List("B", "A", "B"))
val strErrorApplicative = implicitly[Applicative[Validated[String, +?]]]
??? //remove, when you have overridden map2 or ap
strErrorApplicative.map2(valid1, valid2)((a, b) => a + b) shouldBe Valid(3)
strErrorApplicative.map2(invalidA, valid2)((a, b) => a + b) shouldBe invalidA
strErrorApplicative.map2(valid1, invalidB)((a, b) => a + b) shouldBe invalidB
strErrorApplicative.map2(invalidA, invalidB)((a, b) => a + b) shouldBe invalidAB
strErrorApplicative.map2(invalidAB, invalidAB)((a, b) => a + b) shouldBe invalidABAB
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment