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 442 additions and 15 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)(_::_))
package typeclasses
import scala.language.higherKinds
object FunctorOption {
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
//try to write the signature yourself instead of using the IDEs auto-implement
//uncomment and implement:
//implicit val optionFunctor: Functor[Option] = new Functor[Option] {
//}
}