Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • intro-to-fp/short-exercises
  • s472501/short-exercises
  • s410344/short-exercises
3 results
Show changes
Showing
with 462 additions and 37 deletions
package monads
trait Monad[F[_]]:
def pure[A](a: A): F[A]
extension [A](fa: F[A])
def flatMap[B](f: A => F[B]): F[B]
def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] = ???
extension [A](fas: List[F[A]])
def sequence: F[List[A]] = ???
def compose[A, B, C](f: A => F[B])(g: B => F[C]): A => F[C] = ???
trait Functor[F[_]]:
extension [A](fa: F[A])
def map[B](f: A => B): F[B]
object Functor:
def functorFromMonad[F[_]](using m: Monad[F]): Functor[F] = new Functor[F]:
extension [A](fa: F[A])
def map[B](f: A => B): F[B] = ???
package monads
final case class Id[A](value: A)
object Id:
// No tests. If it compiles, it's correct.
given Monad[Id] with
def pure[A](a: A): Id[A] = ???
extension [A](fa: Id[A])
def flatMap[B](f: A => Id[B]): Id[B] = ???
package parsers
import parsers.lib.Monad._
import parsers.lib.Parsers._
import parsers.lib.ToParserOps._
import parsers.lib._
object Combinators:
/* Implement many1, which matches at least one element, returning a NonEmptyList.
* If no element is found, the parser should fail. You may use any other parsers and combinators we have defined.
*/
def many1[A](p: Parser[A]): Parser[NonEmptyList[A]] = ???
def opt[A](pa: Parser[A]): Parser[Option[A]] = ???
package parsers
import parsers.lib.Monad._
import parsers.lib.Parsers._
import parsers.lib.ToParserOps._
import parsers.lib._
case class Contact(name:String, address: Address, phone: Option[Phone])
case class Address(street: String, number: Int, postCode: Int, city: String)
case class Phone(prefix: String, suffix:String)
object ContactParser:
/* This parser should parse strings of the form
* <street> <number>, <postCode> <city>
* As a simplification, we only accept street names without spaces and house numbers without letters.
* Each whitespace in the pattern above should be present (one or more whitespace characters).
*/
def address: Parser[Address] = ???
package parsers.lib
case class NonEmptyList[A](head: A, tail:List[A])
package parsers.lib
sealed trait ParseResult[+A]
final case class Fail(remain: String, error: String) extends ParseResult[Nothing]
final case class Done[A](remain: String, result: A) extends ParseResult[A]
package parsers.lib
/** Our parser trait
* We only have a single function, representing the parsing of a string. So we can declare parsers by writing a
* function literal for such a function
*/
trait Parser[+A]:
def parse(input: String): ParseResult[A]
object Parser:
implicit val parserMonad: Monad[Parser] = new Monad[Parser] {
def pure[A](a: A): Parser[A] = success(a)
def flatMap[A, B](fa: Parser[A])(f: A => Parser[B]): Parser[B] =
input => fa.parse(input) match
case Done(rest, a) => f(a).parse(rest)
case Fail(rest, msg) => Fail(rest, msg)
}
def success[A](a: A): Parser[A] = input => Done(input, a)
def fail[A](message: String): Parser[A] = input => Fail(input, message)
package parsers.lib
trait ParserOps[A]:
val self: Parser[A]
def | [B>:A](pb: Parser[B]): Parser[B] = Parsers.or(self, pb)
def ~[B](next: => Parser[B]): Parser[(A, B)] = Parsers.andThen(self, next)
def many: Parser[List[A]] = Parsers.many(self)
object ToParserOps:
implicit def toParserOps[A](p: Parser[A]): ParserOps[A] =
new ParserOps[A] {
val self: Parser[A] = p
}
package parsers.lib
import Monad._
import scala.util.matching.Regex
import ToParserOps._
trait Parsers:
def char(c: Char): Parser[Char]
def string(s: String): Parser[String]
def regex(r: Regex): Parser[String]
def digits: Parser[String]
def int: Parser[Int]
object Parsers extends Parsers:
def string(s: String): Parser[String] =
input =>
if input.startsWith(s) then Done(input.substring(s.length), s)
else Fail(input, s"""expected "$s"""")
def char(c: Char): Parser[Char] = string(c.toString).map(_.charAt(0))
def regex(r: Regex): Parser[String] =
input =>
r.findPrefixOf(input) match
case Some(m) => Done(input.substring(m.length), m)
case None => Fail(input, s""""expected match for "$r"""")
def digits: Parser[String] = regex("[0-9]+".r)
def int: Parser[Int] = digits.map(_.toInt)
def or[A](pa: Parser[A], pb: Parser[A]): Parser[A] =
input =>
pa.parse(input) match
case Done(rest, out) => Done(rest, out)
case Fail(_, _) => pb.parse(input)
def andThen[A,B](p: Parser[A], next: => Parser[B]): Parser[(A, B)] = for
a <- p
b <- next
yield (a,b)
def many[A](p: Parser[A]): Parser[List[A]] = (
for
a <- p
tail <- many(p)
yield a :: tail
) | Monad[Parser].pure(Nil)
def manyN[A](p: Parser[A], n: Int): Parser[List[A]] =
implicitly[Traverse[List]].sequence(List.fill(n)(p))
/** Match any number of whitespace characters */
val whitespace: Parser[String] = regex(raw"\s*".r)
/** Match any number of non-whitespace characters */
val word: Parser[String] = regex(raw"\S*".r)
// address.parse("Hublandstraße 123, 97074 Würzburg") ==
// Done("", Address("Hublandstraße", 123, 97074, "Würzburg"))
package parsers.lib
object Id:
type Id[A] = A
implicit val idMonad: Monad[Id] = new Monad[Id] {
override def pure[A](a:A): Id[A] = a
override def flatMap[A,B](a:Id[A])(f: A => Id[B]): Id[B] = f(a)
}
trait Functor[F[_]]:
def map[A, B](fa: F[A])(f: A => B): F[B]
object Functor:
def apply[A[_]](implicit F: Functor[A]): Functor[A] = F
trait Applicative[F[_]] extends Functor[F]:
def pure[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))
def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = ap(map(fa)(a => (b:B) => f(a,b)))(fb)
object Applicative:
def apply[A[_]](implicit F: Applicative[A]): Applicative[A] = F
trait Monad[F[_]] extends Applicative[F]:
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
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 pure)
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)))
object Monad:
def apply[A[_]](implicit M: Monad[A]): Monad[A] = M
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)
trait Traverse[F[_]] extends Functor[F]:
def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] =
sequence(map(fa)(f))
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
traverse(fga)(identity)
import Id._
def map[A,B](fa: F[A])(f: A => B): F[B] = traverse[Id,A,B](fa)(a => f(a))
object Traverse:
def apply[A[_]](implicit M: Traverse[A]): Traverse[A] = M
implicit val listTraverse: Traverse[List] = new Traverse[List] {
override def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] =
fa.foldRight(G.pure(List[B]()))((a, b) => G.map2(f(a), b)(_ :: _))
}
package readerwriter
import readerwriter.internal.State, State.*
object Randoms:
def threeInts: State[RNG, (Int, Int, Int)] = ???
def randomInt: State[RNG, Int] = for
rng <- get[RNG]
(rng2, i) = rng.nextInt
_ <- set(rng2)
yield i
def nonNegativeInt: State[RNG, Int] = for
i <- randomInt
yield if i < 0 then -(i + 1) else i
trait RNG:
def nextInt: (RNG, Int)
case class Simple(seed: Long) extends RNG:
/** Basically the same formula as in java.util.Random
* See https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Random.html#next(int) */
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.*
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.{*, given}
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 then
(List("got 1, doing nothing"), 0)
else if n % 2 == 0 then
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 then
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)
given readerM[R]: Monad[[a] =>> Reader[R, a]] with
def pure[A](a: A): Reader[R, A] = Reader(_ => a)
extension [A](fa: Reader[R, A])
def flatMap[B](f: A => Reader[R, B]): Reader[R, B] = Reader(in => {
val a = fa.run(in)
f(a).run(in)
})
override def map2[B, C](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[B](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:
def get[S]: State[S, S] = State(s => (s, s))
def set[S](s: S): State[S, Unit] = State(_ => (s, ()))
given stateMonad[S]: Monad[[a] =>> State[S, a]] with
def pure[A](a: A): State[S, A] = State(s => (s, a))
extension [A](fa: State[S, A])
def flatMap[B](f: A => State[S, B]): State[S, B] = State(s =>
val (s1, a) = fa.run(s)
f(a).run(s1)
)
package readerwriter.internal
import algebra.Monoid
import applicative.Monad
final case class Writer[L, A](v: (L, A))
object Writer:
given [A]: Monoid[List[A]] with
def zero: List[Nothing] = List.empty
def combine(l1: List[A], l2: List[A]): List[A] = l1 ++ l2
def tell[L](l: L): Writer[L, Unit] = Writer((l, ()))
given writerMonad[L: Monoid]: Monad[[a] =>> Writer[L, a]] with
def pure[A](a: A): Writer[L, A] = Writer((summon[Monoid[L]].zero, a))
extension [A](fa: Writer[L, A])
def flatMap[B](f: A => Writer[L, B]): Writer[L, B] =
val next = f(fa.v._2)
Writer((implicitly[Monoid[L]].combine(fa.v._1, next.v._1), next.v._2))
override def map2[B, C](fb: Writer[L, B])(f: (A, B) => C): Writer[L, C] =
Writer((implicitly[Monoid[L]].combine(fa.v._1, fb.v._1), f(fa.v._2, fb.v._2)))
override def map[B](f: A => B): Writer[L, B] = Writer((fa.v._1, f(fa.v._2)))
object Fuse:
/* reduced to relevant parts */
trait Traverse[F[_]]:
def traverse[G[_]:Applicative,A,B](fa: F[A])(f: A => G[B]): G[F[B]] =
sequence(map(fa)(f))
def sequence[G[_]:Applicative,A](fma: F[G[A]]): G[F[A]] =
traverse(fma)(ma => ma)
def map[A,B](fa: F[A])(f: A => B): F[B] = ??? // exercise sheet
/* Exercise: implement fusing two traversal functions in one
traversal using applicative products */
def fuse[G[_],H[_],A,B](fa: F[A])(f: A => G[B], g: A => H[B])
(implicit G: Applicative[G],H: Applicative[H])
: (G[F[B]], H[F[B]]) = ???
trait Applicative[F[_]]:
def map2[A,B,C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] =
apply(map(fa)(f.curried))(fb)
def apply[A,B](fab: F[A => B])(fa: F[A]): F[B] =
map2(fab, fa)(_(_))
def pure[A](a: => A): F[A]
def map[A,B](fa: F[A])(f: A => B): F[B] =
apply(pure(f))(fa)
def product[G[_]](G: Applicative[G]): Applicative[[x] =>> (F[x], G[x])] =
val self = this
new Applicative {
def pure[A](a: => A) = (self.pure(a), G.pure(a))
override def apply[A,B](fs: (F[A => B], G[A => B]))(p: (F[A], G[A])) =
(self.apply(fs._1)(p._1), G.apply(fs._2)(p._2))
}
object SequenceMap:
trait Applicative[F[_]]:
// Exercise: implement sequence for maps instead of lists
def sequenceMap[K,V](m: Map[K, F[V]]): F[Map[K,V]] = ???
//you can use any of these methods
def pure[A](a: => A): F[A]
def apply[A,B](fab: F[A => B])(fa: F[A]): F[B] = map2(fab, fa)((ab, a) => ab(a))
def map[A,B](fa: F[A])(f: A => B): F[B] = apply(pure(f))(fa)
def map2[A,B,C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] =
apply(apply(pure(f.curried))(fa))(fb)
def map3[A,B,C,D](fa: F[A], fb: F[B], fc: F[C])(f: (A, B, C) => D): F[D] =
apply(apply(apply(pure(f.curried))(fa))(fb))(fc)
def factor[A,B](fa: F[A], fb: F[B]): F[(A,B)] =
map2(fa, fb)((_,_))
def sequence[A](fas: List[F[A]]): F[List[A]] =
fas.foldRight[F[List[A]]](pure(Nil))((a, b) => map2(a, b)(_::_))
def traverse[A,B](fas: List[A])(f: A => F[B]): F[List[B]] =
fas.foldRight[F[List[B]]](pure(Nil))((a, b) => map2(f(a), b)(_::_))
......@@ -2,73 +2,56 @@ package algebra
import org.scalatest._
import testutil.PendingIfUnimplemented
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class MonoidsSpec extends FlatSpec with Matchers with AppendedClues with PendingIfUnimplemented {
class MonoidsSpec extends AnyFlatSpec with Matchers with AppendedClues with PendingIfUnimplemented:
"The addditive Int monoid" should "have the right zero element" in {
Monoids.intAddition.zero shouldBe 0
intAddition.zero shouldBe 0
}
it should "combine multiple values correctly" in {
Monoids.intAddition.op(1, 2) shouldBe 3
intAddition.combine(1, 2) shouldBe 3
}
"The multiplicative Int monoid" should "have the right zero element" in {
Monoids.intMultiplication.zero shouldBe 1
intMultiplication.zero shouldBe 1
}
it should "combine multiple values correctly" in {
Monoids.intMultiplication.op(1, 2) shouldBe 2
intMultiplication.combine(1, 2) shouldBe 2
}
"The boolean or monoid" should "have the right zero element" in {
Monoids.booleanOr.zero shouldBe false
booleanOr.zero shouldBe false
}
it should "combine multiple values correctly" in {
Monoids.booleanOr.op(false, true) shouldBe true
Monoids.booleanOr.op(false, false) shouldBe false
booleanOr.combine(false, true) shouldBe true
booleanOr.combine(false, false) shouldBe false
}
"The boolean and monoid" should "have the right zero element" in {
Monoids.booleanAnd.zero shouldBe true
booleanAnd.zero shouldBe true
}
it should "combine multiple values correctly" in {
Monoids.booleanAnd.op(false, true) shouldBe false
Monoids.booleanAnd.op(false, false) shouldBe false
Monoids.booleanAnd.op(true, true) shouldBe true
booleanAnd.combine(false, true) shouldBe false
booleanAnd.combine(false, false) shouldBe false
booleanAnd.combine(true, true) shouldBe true
}
"The option monoid" should "always return the non-empty option" in {
Monoids.optionMonoid[Int].op(Some(3), None) shouldBe Some(3)
Monoids.optionMonoid[Int].op(None, Some(3)) shouldBe Some(3)
optionMonoid[Int].combine(Some(3), None) shouldBe Some(3)
optionMonoid[Int].combine(None, Some(3)) shouldBe Some(3)
}
"The endofunction monoid" should "always give the right zero element" in {
Monoids.endoMonoid[Int].zero(3) shouldBe 3
endoMonoid[Int].zero(3) shouldBe 3
}
it should "combine the functions one way or the other" in {
Monoids.endoMonoid[Int].op(_ / 4, _ * 2)(4) shouldBe 2
}
"the foldMap implementation" should "give the right result" in {
val l = List("1", "2", "3")
Monoids.foldMap(l, new Monoid[Int] {
def zero = 10
def op(a: Int, b: Int) = a + b
})(_.toInt) shouldBe 16
}
"the balanced foldMap implementation" should "give the right result" in {
val l = Vector("1", "2", "3")
Monoids.foldMapBalanced(l, new Monoid[Int] {
def zero = 0
def op(a: Int, b: Int) = a + b
})(_.toInt) shouldBe 6
endoMonoid[Int].combine(_ / 4, _ * 2)(4) shouldBe 2
}
"the bag function" should "count correctly" in {
val words = "a rose is a rose".split(" ")
val words = "a rose is a rose".split(" ").toList
Monoids.bag(words) shouldBe Map("a" -> 2, "rose" -> 2, "is" -> 1)
bag(words) shouldBe Map("a" -> 2, "rose" -> 2, "is" -> 1)
}
}