Commit 4fa22fbb authored by crater2150's avatar crater2150

[lec09] Adding Readers, Writers and State Randoms

parent 145200df
......@@ -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,
......
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)
}
}
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] = ???
}
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)
}
}
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)))
}
}
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, ()))
}
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)))
}
}
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)
}
}
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
}
}
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"
}
}
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
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment