diff --git a/README.md b/README.md
index 0efc9867441063c6dcff25492099004967eb3c4f..4484cf83d29013280d08cc6155ffff1d4c307a6b 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ usually marked with `???`.
 | 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)
+| 8: Algebraic View on more Monads| [`readerwriter`](src/main/scala/readerwriter/)                    | `testOnly readerwriter.*`
 
 ## Usage tips:
 To keep your local solutions to the exercises when pulling from the repository,
diff --git a/src/main/scala/readerwriter/Randoms.scala b/src/main/scala/readerwriter/Randoms.scala
new file mode 100644
index 0000000000000000000000000000000000000000..04c7a0ddb3229e76fb8febd6ed2a95b188dbd7f4
--- /dev/null
+++ b/src/main/scala/readerwriter/Randoms.scala
@@ -0,0 +1,34 @@
+package readerwriter
+
+import readerwriter.internal.State, State._
+import util._
+
+object Randoms {
+  def threeInts: State[RNG, (Int, Int, Int)] = ???
+
+  /*@formatter:off*/
+  def randomInt: State[RNG, Int] = for {
+    rng      <- get[RNG]
+    (rng2, i) = rng.nextInt
+    _        <- set(rng2)
+  } yield i
+  /*@formatter:on*/
+
+
+  def nonNegativeInt: State[RNG, Int] = for {
+    i <- randomInt
+  } yield if (i < 0) -(i + 1) else i
+}
+
+trait RNG {
+  def nextInt: (RNG, Int)
+}
+
+case class Simple(seed: Long) extends RNG {
+  def nextInt: (RNG, Int) = {
+    val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
+    val nextRNG = Simple(newSeed)
+    val n = (newSeed >>> 16).toInt
+    (nextRNG, n)
+  }
+}
diff --git a/src/main/scala/readerwriter/Readers.scala b/src/main/scala/readerwriter/Readers.scala
new file mode 100644
index 0000000000000000000000000000000000000000..fb66563ec983eca3498bc311058ec078ec8c0117
--- /dev/null
+++ b/src/main/scala/readerwriter/Readers.scala
@@ -0,0 +1,25 @@
+package readerwriter
+
+import java.time.LocalDate
+
+import readerwriter.internal.Reader, Reader._
+import util._
+
+final case class Request(
+    user: Option[String],
+    locale: String,
+    route: String,
+    params: Map[String, List[String]],
+    now: LocalDate,
+)
+
+object Readers {
+  /* everything required to use ask and the monad operations
+   * is already imported */
+
+  def formatUser: Reader[Request, String] = ???
+
+  def formatTime: Reader[Request, String] = ???
+
+  def sayBye: Reader[Request, String] = ???
+}
diff --git a/src/main/scala/readerwriter/Writers.scala b/src/main/scala/readerwriter/Writers.scala
new file mode 100644
index 0000000000000000000000000000000000000000..cd90b40ae8778c2ae36962a9d1ed2b32678b61b3
--- /dev/null
+++ b/src/main/scala/readerwriter/Writers.scala
@@ -0,0 +1,39 @@
+package readerwriter
+
+
+import readerwriter.internal.Writer, Writer._
+import util._
+
+
+object Writers {
+  /* everything required to use tell and the monad operations
+   * is already imported */
+
+  def collatzDepth(n: Int): Writer[List[String], Int] = ???
+
+  def collatzSearch(start: Int, limit: Int): Writer[List[String], Int] = ???
+
+}
+
+object CollatzWithoutWriter {
+  def collatzDepth(n: Int): (List[String], Int) =
+    if (n == 1)
+      (List("got 1, doing nothing"), 0)
+    else if (n % 2 == 0) {
+      val (way, depth) = collatzDepth(n / 2)
+      (s"got $n, halving" :: way, depth + 1)
+    } else {
+      val (way, depth) = collatzDepth(n * 3 + 1)
+      (s"got $n, tripling plus one" :: way, depth + 1)
+    }
+
+  def collatzSearch(start: Int, limit: Int): (List[String], Int) = {
+    val (way, depth) = collatzDepth(start)
+    if (depth < limit) {
+      val (way2, number) = collatzSearch(start + 1, limit)
+      (s"testing $start" :: way ++ (s"depth was $depth" :: way2), number)
+    } else
+      (s"testing $start" :: way ++ List(s"depth was $depth", s"returning $start"), start)
+  }
+}
+
diff --git a/src/main/scala/readerwriter/internal/Reader.scala b/src/main/scala/readerwriter/internal/Reader.scala
new file mode 100644
index 0000000000000000000000000000000000000000..ce27edd7aa79a8ecb71ab3f53e5d2ca26db687da
--- /dev/null
+++ b/src/main/scala/readerwriter/internal/Reader.scala
@@ -0,0 +1,23 @@
+package readerwriter.internal
+
+import applicative.Monad
+
+case class Reader[R, A](run: R => A)
+
+object Reader {
+  def ask[R]: Reader[R, R] = Reader(x => x)
+
+  implicit def readerM[R]: Monad[Reader[R, ?]] = new Monad[Reader[R, ?]] {
+    override def flatMap[A, B](fa: Reader[R, A])(f: A => Reader[R, B]): Reader[R, B] = Reader(in => {
+      val a = fa.run(in)
+      f(a).run(in)
+    })
+    override def unit[A](a: A): Reader[R, A] = Reader(_ => a)
+    override def map2[A, B, C](fa: Reader[R, A], fb: Reader[R, B])(f: (A, B) => C): Reader[R, C] = Reader(in => {
+      val a = fa.run(in)
+      val b = fb.run(in)
+      f(a, b)
+    })
+    override def map[A, B](fa: Reader[R, A])(f: A => B): Reader[R, B] = Reader(in => f(fa.run(in)))
+  }
+}
diff --git a/src/main/scala/readerwriter/internal/State.scala b/src/main/scala/readerwriter/internal/State.scala
new file mode 100644
index 0000000000000000000000000000000000000000..2d93948190db057c8f91d26f59e0e7488dd134de
--- /dev/null
+++ b/src/main/scala/readerwriter/internal/State.scala
@@ -0,0 +1,21 @@
+package readerwriter.internal
+/* You don't need to edit any of the files inside this package. These are the actual implementations, and you should
+ * only use the provided methods, not rely on internal structure
+ */
+
+import applicative.Monad
+
+case class State[S, A] private (run: S => (S, A))
+
+case object State {
+  implicit def stateMonad[S]: Monad[State[S, ?]] = new Monad[State[S, ?]] {
+    override def unit[A](a: A): State[S, A] = State(s => (s, a))
+    override def flatMap[A, B](fa: State[S, A])(f: A => State[S, B]): State[S, B] = State(s => {
+      val (s1, a) = fa.run(s)
+      f(a).run(s1)
+    })
+  }
+
+  def get[S]: State[S, S] = State(s => (s, s))
+  def set[S](s: S): State[S, Unit] = State(_ => (s, ()))
+}
diff --git a/src/main/scala/readerwriter/internal/Writer.scala b/src/main/scala/readerwriter/internal/Writer.scala
new file mode 100644
index 0000000000000000000000000000000000000000..1c9ccf849d70d162fbf4457a427ec0a37a24a77d
--- /dev/null
+++ b/src/main/scala/readerwriter/internal/Writer.scala
@@ -0,0 +1,27 @@
+package readerwriter.internal
+
+import algebra.Monoid
+import applicative.Monad
+
+final case class Writer[L, A](v: (L, A))
+
+object Writer {
+  implicit def listMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
+    def zero: List[Nothing] = List.empty
+    def op(l1: List[A], l2: List[A]): List[A] = l1 ++ l2
+  }
+
+  def tell[L](l: L): Writer[L, Unit] = Writer((l, ()))
+
+  implicit def writerM[L: Monoid]: Monad[Writer[L, ?]] = new Monad[Writer[L, ?]] {
+    override def flatMap[A, B](fa: Writer[L, A])(f: A => Writer[L, B]): Writer[L, B] = {
+      val next = f(fa.v._2)
+      Writer((implicitly[Monoid[L]].op(fa.v._1, next.v._1), next.v._2))
+    }
+
+    def unit[A](a: A): Writer[L, A] = Writer((implicitly[Monoid[L]].zero, a))
+    override def map2[A, B, C](fa: Writer[L, A], fb: Writer[L, B])(f: (A, B) => C): Writer[L, C] =
+      Writer((implicitly[Monoid[L]].op(fa.v._1, fb.v._1), f(fa.v._2, fb.v._2)))
+    override def map[A, B](fa: Writer[L, A])(f: A => B): Writer[L, B] = Writer((fa.v._1, f(fa.v._2)))
+  }
+}
diff --git a/src/main/scala/util/package.scala b/src/main/scala/util/package.scala
new file mode 100644
index 0000000000000000000000000000000000000000..81f7365163f8ecacac41a0224f197d1b4c263863
--- /dev/null
+++ b/src/main/scala/util/package.scala
@@ -0,0 +1,10 @@
+import applicative.Monad
+
+import scala.language.higherKinds
+
+package object util {
+  implicit class MonadOps[F[_] : Monad, A](fa: F[A]) {
+    def flatMap[B](f: A => F[B]): F[B] = implicitly[Monad[F]].flatMap(fa)(f)
+    def map[B](f: A => B): F[B] = implicitly[Monad[F]].map(fa)(f)
+  }
+}
diff --git a/src/test/scala/readerwriter/RandomSpec.scala b/src/test/scala/readerwriter/RandomSpec.scala
new file mode 100644
index 0000000000000000000000000000000000000000..9cb491670c000b3ad40cd0fdd83f394dd1941917
--- /dev/null
+++ b/src/test/scala/readerwriter/RandomSpec.scala
@@ -0,0 +1,18 @@
+package readerwriter
+
+import org.scalatest._
+import testutil.PendingIfUnimplemented
+
+class RandomSpec extends FlatSpec with Matchers with AppendedClues with PendingIfUnimplemented {
+  val initial = Simple(192837465L)
+  val (r1, i1) = initial.nextInt
+  val (r2, i2) = r1.nextInt
+  val (r3, i3) = r2.nextInt
+
+  "threeInts" should "have the same result as passing state manually" in {
+    Randoms.threeInts.run(initial)._2 shouldBe ((i1, i2, i3))
+  }
+  it should "result in the same rng state as passing state manually" in {
+    Randoms.threeInts.run(initial)._1 shouldBe r3
+  }
+}
diff --git a/src/test/scala/readerwriter/ReaderSpec.scala b/src/test/scala/readerwriter/ReaderSpec.scala
new file mode 100644
index 0000000000000000000000000000000000000000..b4500ffcbc5f08fe07581f39d8acf1bbae1cff3b
--- /dev/null
+++ b/src/test/scala/readerwriter/ReaderSpec.scala
@@ -0,0 +1,31 @@
+package readerwriter
+
+import org.scalatest._
+import testutil.PendingIfUnimplemented
+import java.time.LocalDate
+
+class ReaderSpec extends FlatSpec with Matchers with AppendedClues with PendingIfUnimplemented {
+  val exampleRequest = Request(
+    Some("Mister X"),
+    "de-DE",
+    "/hello",
+    Map(),
+    LocalDate.of(2019,6,26)
+  )
+
+  val noUserRequest = exampleRequest.copy(user = None)
+
+  "formatUser" should "read the username or substitute \"anonymous\"" in {
+    Readers.formatUser.run(exampleRequest) shouldBe "Mister X"
+    Readers.formatUser.run(noUserRequest) shouldBe "anonymous"
+  }
+
+  "formatTime" should "output the string representation of a request date" in {
+    Readers.formatTime.run(exampleRequest) shouldBe "2019-06-26"
+  }
+
+  "sayBye" should "contain the correct user name and date" in {
+    Readers.sayBye.run(exampleRequest) shouldBe "Goodbye Mister X, today is 2019-06-26"
+  }
+}
+
diff --git a/src/test/scala/readerwriter/WriterSpec.scala b/src/test/scala/readerwriter/WriterSpec.scala
new file mode 100644
index 0000000000000000000000000000000000000000..8a51b3a37f86c0ad3f869b671b7ef8381664de98
--- /dev/null
+++ b/src/test/scala/readerwriter/WriterSpec.scala
@@ -0,0 +1,57 @@
+package readerwriter
+
+import java.time.LocalDate
+
+import org.scalatest._
+import testutil.PendingIfUnimplemented
+
+class WriterSpec extends FlatSpec with Matchers with AppendedClues with PendingIfUnimplemented {
+  "collatzDepth" should "keep a log of the search" in {
+    val (log, _) = Writers.collatzDepth(3).v
+    log shouldBe List(
+      "got 3, tripling plus one",
+      "got 10, halving",
+      "got 5, tripling plus one",
+      "got 16, halving",
+      "got 8, halving",
+      "got 4, halving",
+      "got 2, halving",
+      "got 1, doing nothing",
+    )
+  }
+
+  it should "return the right depth" in {
+    val (_, depth) = Writers.collatzDepth(3).v
+    depth shouldBe 7
+  }
+
+  "collatzSearch" should "keep a log, together with collatzDepth" in {
+    val (log, _) = Writers.collatzSearch(1, 5).v
+    log shouldBe List(
+      "testing 1",
+      "got 1, doing nothing",
+      "depth was 0",
+      "testing 2",
+      "got 2, halving",
+      "got 1, doing nothing",
+      "depth was 1",
+      "testing 3",
+      "got 3, tripling plus one",
+      "got 10, halving",
+      "got 5, tripling plus one",
+      "got 16, halving",
+      "got 8, halving",
+      "got 4, halving",
+      "got 2, halving",
+      "got 1, doing nothing",
+      "depth was 7",
+      "returning 3",
+    )
+  }
+
+  it should "find the smallest number with at least given depth" in {
+    val (_, res) = Writers.collatzSearch(1, 5).v
+    res shouldBe 3
+  }
+}
+