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
Commits on Source (30)
Showing
with 287 additions and 87 deletions
...@@ -8,15 +8,19 @@ To mark your progress during the lecture, please vote on ...@@ -8,15 +8,19 @@ To mark your progress during the lecture, please vote on
## Overview: ## Overview:
Templates are within `src/main/scala`. Parts that you should complete are Templates are within `src/main/scala`. Parts that you should complete are
usually marked with `???`. usually marked with `???`. Lectures not listed here are not migrated to Scala
3 yet, please run `git pull` before the lecture.
| lecture | package | tests | lecture | package | tests
|---------------------------------|-------------------------------------------------------------------|---------------------------- |---------------------------------|-------------------------------------------------------------------|----------------------------
| 2: Functional Data Structures | [`datastructures`](src/main/scala/datastructures) | `testOnly datastructures.*` | 2: Functional Data Structures | [`datastructures`](src/main/scala/datastructures) | `testOnly datastructures.*`
| 3: Error Handling | [`errors`](src/main/scala/errors) | `testOnly errors.*` | 3: Error Handling | [`errors`](src/main/scala/errors) | `testOnly errors.*`
| 4: Laziness | [`laziness`](src/main/scala/laziness/) | `testOnly laziness.*` | 4: Laziness | [`laziness`](src/main/scala/laziness/) | `testOnly laziness.*`
| 5: Algebras, Laws, and Monoids | [`algebra`](src/main/scala/algebra/) | `testOnly algebra.*` | 5: Algebras, Laws and monoids | [`algebra`](src/main/scala/algebra/) | `testOnly algebra.*`
| 6: Typeclasses | [`typeclasses`](src/main/scala/typeclasses/) | `testOnly typeclasses.*` (needs some uncommenting) | 6: Foldables and Functors | [`foldfunc`](src/main/scala/foldfunc/) | `testOnly foldfunc.*`
| 7: Monads | [`monads`](src/main/scala/monads/) | `testOnly monads.*`
| 8: Applicative Functors | [`applicative`](src/main/scala/applicative/) | `testOnly applicative.*`
| 9: An algebraic View on Monads | [`readerwriter`](src/main/scala/readerwriter/) | `testOnly readerwriter.*`
## Usage tips: ## Usage tips:
To keep your local solutions to the exercises when pulling from the repository, To keep your local solutions to the exercises when pulling from the repository,
......
name := "short-exercises" name := "short-exercises"
organization := "de.uniwue.fp" organization := "de.uniwue.fp"
version := "0.1-SNAPSHOT" version := "0.1-SNAPSHOT"
scalaVersion := "2.12.8" scalaVersion := "3.2.2"
scalacOptions ++= Seq( scalacOptions ++= Seq(
"-deprecation", "-deprecation",
"-encoding", "UTF-8", "-encoding", "UTF-8",
"-feature", "-feature",
"-unchecked", "-unchecked",
"-Xlint", "-language:higherKinds",
"-Yno-adapted-args", "-Ykind-projector:underscores",
"-Ywarn-dead-code",
"-Ywarn-numeric-widen",
"-Ywarn-value-discard",
"-Ypartial-unification",
"-Xfuture",
"-Ywarn-unused-import",
"-Ywarn-unused:implicits",
"-Ywarn-unused:locals",
"-Ywarn-unused:params",
"-Ywarn-unused:patvars",
"-Ywarn-unused:privates",
"-Ypatmat-exhaust-depth", "40"
) )
resolvers += Resolver.sonatypeRepo("releases") resolvers += Resolver.sonatypeRepo("releases")
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.3")
// Disallow some language construcs // Disallow some language construcs
// your bonus exercises will have to compile with these options // your bonus exercises will have to compile with these options
addCompilerPlugin("org.wartremover" %% "wartremover" % "2.4.1") addCompilerPlugin("org.wartremover" %% "wartremover" % "3.1.1" cross CrossVersion.full)
scalacOptions ++= Seq( scalacOptions ++= Seq(
"-P:wartremover:traverser:org.wartremover.warts.AsInstanceOf", "-P:wartremover:traverser:org.wartremover.warts.AsInstanceOf",
"-P:wartremover:traverser:org.wartremover.warts.IsInstanceOf", "-P:wartremover:traverser:org.wartremover.warts.IsInstanceOf",
...@@ -42,5 +28,6 @@ scalacOptions ++= Seq( ...@@ -42,5 +28,6 @@ scalacOptions ++= Seq(
"-P:wartremover:traverser:org.wartremover.warts.While", "-P:wartremover:traverser:org.wartremover.warts.While",
) )
libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.5" libraryDependencies += "org.scalactic" %% "scalactic" % "3.2.11"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.11" % "test"
libraryDependencies += "org.typelevel" %% "cats-core" % "2.7.0"
sbt.version=1.6.2
package algebra package algebra
trait Monoid[A] { trait Monoid[A]:
def op(a1: A, a2: A): A def combine(a1: A, a2: A): A
def zero: A def zero: A
}
extension (a1: A)
def |+| (a2: A) = combine(a1, a2)
package algebra package algebra
object Monoids { def intAddition: Monoid[Int] = new Monoid:
def intAddition: Monoid[Int] = new Monoid[Int] { def zero = ???
def zero = ??? def combine(a: Int, b: Int): Int = ???
def op(a: Int, b: Int): Int = ???
}
def intMultiplication: Monoid[Int] = ??? def intMultiplication: Monoid[Int] = new Monoid:
def zero = ???
def combine(a: Int, b: Int): Int = ???
def booleanOr: Monoid[Boolean] = ??? def booleanOr: Monoid[Boolean] = new Monoid:
def zero = ???
def combine(a: Boolean, b: Boolean): Boolean = ???
def booleanAnd: Monoid[Boolean] = ??? def booleanAnd: Monoid[Boolean] = new Monoid:
def zero = ???
def combine(a: Boolean, b: Boolean): Boolean = ???
def optionMonoid[A]: Monoid[Option[A]] = ??? def optionMonoid[A]: Monoid[Option[A]] = ???
def endoMonoid[A]: Monoid[A => A] = ??? def endoMonoid[A]: Monoid[A => A] = ???
def foldMap[A, B](as: List[A], m: Monoid[B])(f: A => B): B = ??? def bag[A](as: List[A]): Map[A, Int] = ???
def foldMapBalanced[A, B](as: IndexedSeq[A], m: Monoid[B])(f: A => B): B = ??? /* merges maps, if their value type has a monoid. See lecture */
given mapMergeMonoid[K,V](using MV: Monoid[V]): Monoid[Map[K, V]] =
new Monoid[Map[K, V]]:
def zero = Map[K,V]()
def combine(a: Map[K, V], b: Map[K, V]) =
(a.keySet ++ b.keySet).foldLeft(zero) ( (acc,k) =>
acc + (
k -> (a.getOrElse(k, MV.zero) |+| b.getOrElse(k, MV.zero))
)
)
def mapMergeMonoid[K,V](V: Monoid[V]): Monoid[Map[K, V]] = /* Uses a monoid to combine all elements of a list into one. See lecture */
new Monoid[Map[K, V]] { def combineAll[A](as: List[A])(using m: Monoid[A]): A =
def zero = Map[K,V]() as.foldLeft(m.zero)(m.combine)
def op(a: Map[K, V], b: Map[K, V]) =
(a.keySet ++ b.keySet).foldLeft(zero) { (acc,k) =>
acc + (k ->
V.op(a.getOrElse(k, V.zero), b.getOrElse(k, V.zero)))
}
}
def bag[A](as: IndexedSeq[A]): Map[A, Int] = ???
}
package applicative
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] = ff.map2(fa)((f, a) => f(a))
extension [A](fa: F[A])
// implement map using pure and ap, but not flatMap/flatten
def map[B](f: A => B): F[B] = ???
// implement map2 using pure and ap
// hint: f.curried converts `(A,B) => C` to `A => (B => C)`
def map2[B,C](fb: F[B])(f: (A, B) => C): F[C] = ???
object Applicative:
// implement pure first
// then implement ap and map2
def compose[F[_], G[_]](
using F: Applicative[F], G: Applicative[G]
): Applicative[[a] =>> F[G[a]]] =
new Applicative[[a] =>> F[G[a]]]:
def pure[A](a: A) = ???
// implement one of these:
//override def ap[A,B](fgf: F[G[A => B]])(fga: F[G[A]]): F[G[B]] = ???
//extension [A](fga: F[G[A]])
// override def map2[B,C](fgb: F[G[B]])(f: (A, B) => C): F[G[C]] = ???
package applicative
trait Functor[F[_]]:
extension [A](fa: F[A])
def map[B](f: A => B): F[B]
package applicative
trait Monad[F[_]] extends Applicative[F]:
extension [A](fa: F[A])
def flatMap[B](f: A => F[B]): F[B]
override def map[B](f: A => B): F[B] = fa.flatMap((a:A) => pure(f(a)))
override def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] =
fa.flatMap(a => fb.flatMap(b => pure(f(a, b))))
def flatten[A](ffa: F[F[A]]): F[A] = flatMap(ffa)(fa => fa)
def map3[A, B, C, D](fa: F[A], fb: F[B], fc: F[C])(f: (A, B, C) => D): F[D] =
fa.flatMap(a => fb.flatMap(b => fc.flatMap(c => pure(f(a, b, c)))))
def sequence[A](fas: List[F[A]]): F[List[A]] =
fas.foldRight[F[List[A]]](pure(Nil))((a, b) => a.map2(b)(_::_))
def compose[A, B, C](f: A => F[B])(g: B => F[C]): A => F[C] =
a => flatMap(f(a))(g)
package applicative
enum Validated[+E, +A]:
case Valid(a:A)
case Invalid(head: E, tail: List[E])
import Validated.*
object Validated:
given validatedApplicative[E]: Applicative[[a] =>> Validated[E, a]] with
def pure[A](a: A) = Valid(a)
// add map2 or ap here
//extension [A](fa: Validated[E,A])
// override def map2[B,C](fb: Validated[E,B])(f: (A,B) => C) = ???
//override def ap[A,B](ff: Validated[E,A => B])(fa: Validated[E,A]) =
package datastructures package datastructures
sealed trait List[+A] { enum List[+A]:
def head = this match { case Nil
case Cons(_head: A, _tail: List[A])
def head: A = this match
case Nil => sys.error("head of empty list") case Nil => sys.error("head of empty list")
case Cons(a, _) => a case Cons(a, _) => a
}
/** removes the first element of a list and returns the rest */
def tail: List[A] = ??? def tail: List[A] = ???
/** returns all but the last element of a list */
def init: List[A] = ??? def init: List[A] = ???
/** replaces the first element of a list */
def setHead[AA >: A](head: AA): List[AA] = ??? def setHead[AA >: A](head: AA): List[AA] = ???
def foldLeft[B](z: B)(f: (B, A) => B): B = ??? /** recurses through the list, combining elements with the given function
} * Uncomment the annotation to enable checking for tail-recursiveness */
case object Nil extends List[Nothing] //@annotation.tailrec
case class Cons[+A](elem: A, rest: List[A]) extends List[A] final def foldLeft[B](z: B)(f: (B, A) => B): B = ???
object List { object List:
/** construct a list by passing elements
*
* Remember: `apply` makes the object behave like a function,
* you can call it as `List(elem1, elem2,...)`
**/
def apply[A](as: A*): List[A] = def apply[A](as: A*): List[A] =
if (as.isEmpty) Nil if as.isEmpty then Nil
else Cons(as.head, apply(as.tail: _*)) else Cons(as.head, apply(as.tail: _*))
}
package datastructures package datastructures
object Parametricity { object Parametricity:
// with our compiler settings from build.sbt, there are even less // with our compiler settings from build.sbt, there are even less
// possibilities to do something wrong here without the compiler complaining // possibilities to do something wrong here without the compiler complaining
def para[A,B,C](a: A, b: B)(f: (A,B) => C): C = ??? def para[A,B,C](a: A, b: B)(f: (A,B) => C): C = ???
}
package errors package errors
sealed trait Either[+E, +A] { enum Either[+E, +A]:
def map[B](f: A => B): Either[E, B] = this match { case Left(value: E)
case Right(value: A)
def map[B](f: A => B): Either[E, B] = this match
case Left(e) => Left(e) case Left(e) => Left(e)
case Right(a) => Right(f(a)) case Right(a) => Right(f(a))
}
def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match{ def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match
case Left(e) => Left(e) case Left(e) => Left(e)
case Right(a) => f(a) case Right(a) => f(a)
}
def map2[EE >: E, B, C](other: Either[EE, B])(f: (A, B) => C): Either[EE, C] = ??? def map2[EE >: E, B, C](other: Either[EE, B])(f: (A, B) => C): Either[EE, C] = ???
}
final case class Left[+E](value: E) extends Either[E, Nothing]
final case class Right[+A](value: A) extends Either[Nothing, A]
package errors package errors
sealed trait Option[+A] { enum Option[+A]:
case Some(get: A)
case None
def map[B](f: A => B): Option[B] = ??? def map[B](f: A => B): Option[B] = ???
def getOrElse[B >: A](default: => B): B = ??? def getOrElse[B >: A](default: => B): B = ???
...@@ -8,11 +11,7 @@ sealed trait Option[+A] { ...@@ -8,11 +11,7 @@ sealed trait Option[+A] {
def flatMap[B](f: A => Option[B]): Option[B] = ??? def flatMap[B](f: A => Option[B]): Option[B] = ???
def filter[B](f: A => Boolean): Option[A] = ??? def filter[B](f: A => Boolean): Option[A] = ???
}
final case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]
object Option { object Option:
def sequence[A](list: List[Option[A]]): Option[List[A]] = ??? def sequence[A](list: List[Option[A]]): Option[List[A]] = ???
}
package errors package errors
object Team { // This allows us to write Some and None without prefix, as we would inside the Option enum
val persons = List( import Option.{None, Some}
Person("Dagobert", "Finanzabteilung"),
Person("Donald", "Spassabteilung"),
Person("Daniel", "R&D"),
)
type Team = (Person, Person) val persons = List(
Person("Dagobert", "Finanzabteilung"),
Person("Donald", "Spassabteilung"),
Person("Daniel", "R&D"),
)
type Team = (Person, Person)
def lookup(name: String): Option[Person] = def lookup(name: String): Option[Person] =
persons.find(_.name == name).fold(None: Option[Person])(a => Some(a)) persons.find(_.name == name).fold(None)(a => Some(a))
def getTeam(name1: String, name2: String): Option[Team] = ??? def getTeam(name1: String, name2: String): Option[Team] = ???
}
final case class Person( final case class Person(
name: String, name: String,
......
package foldfunc
import algebra.Monoid
object Foldable:
/* Implement this using a single foldLeft (no map and no combineAll) */
def foldMap[A,B](as: List[A])(f: A => B)(using m: Monoid[B]): B = ???
// uncomment and implement the Foldable instance for List
//given Foldable[List] with
/* Implement a generic toList method for any foldable */
//def toList
trait Foldable[F[_]]:
extension [A](as: F[A])
def foldRight[B](z: B)(f: (A,B) => B): B
def foldLeft[B](z: B)(f: (B,A) => B): B
def foldMap[B](f: A => B)(using mb: Monoid[B]): B =
foldLeft(mb.zero)((b, a) => mb.combine(b, f(a)))
def combineAll(m: Monoid[A]): A =
foldLeft(m.zero)(m.combine)
package foldfunc
trait Functor[F[_]]:
extension [A](fa: F[A])
def map[B](f: A => B): F[B]
//try to write the signature yourself instead of using the IDEs auto-implement
//uncomment and implement:
//given Functor[Option] with
//...
package foldfunc
case class Person(lastName: String, firstName: String, age: Int)
object Person:
val janedoe = Person("Doe", "Jane", 42)
val odersky = Person("Odersky", "Martin", 62)
val curry = Person("Curry", "Haskell", 121)
package typeclasses package foldfunc
// Create the Show typeclass here // Create the Show typeclass here
package typeclasses package foldfunc
object ShowMain {
/* Remove this line after the Show exercises. The below methods should compile and the main should output two lines. /* Remove this line after the Show exercises. The below methods should compile and the main should output two lines.
def main(args: Array[String]): Unit = { @main def main(): Unit =
printList(List(Person.odersky, Person.curry)) printList(List(Person.janedoe, Person.odersky, Person.curry))
/* /*
expected output: expected output:
Martin Odersky is 60 years old Jane Doe is 42 years old
Haskell Curry is 119 years old Martin Odersky is 62 years old
Haskell Curry is 121 years old
*/ */
}
def printList[A : Show](as: List[A]): Unit ={ def printList[A](as: List[A])(using Show[A]): Unit =
as.foreach(a => println(implicitly[Show[A]].show(a))) as.foreach(a => println(a.show)
}
// */ // */
}
package laziness package laziness
sealed trait Stream[+A] { enum LazyList[+A]:
case Empty
case Cons(h: () => A, t: () => LazyList[A])
// uncomment to be able to use cons(h, t) and empty directly // uncomment to be able to use cons(h, t) and empty directly
// imports methods from the companion object // imports methods from the companion object
//import Stream._ //import LazyList._
def toList: List[A] = ??? def toList: List[A] = ???
def take(n: Int): Stream[A] = ??? def take(n: Int): LazyList[A] = ???
//This can be tailrecursive. Uncomment below to let the compiler check //This can be tailrecursive. Uncomment below to let the compiler check for it.
//@annotation.tailrec //@annotation.tailrec
def drop(n: Int): Stream[A] = ??? def drop(n: Int): LazyList[A] = ???
// Methods shown during lecture // Methods shown during lecture
def headOption: Option[A] = this match { def headOption: Option[A] = this match
case Cons(h, _) => Some(h()) case Cons(h, _) => Some(h())
case Empty => None case Empty => None
}
def exists(p: A => Boolean): Boolean = this match { def exists(p: A => Boolean): Boolean = this match
case Cons(x, xs) => p(x()) || xs().exists(p) case Cons(x, xs) => p(x()) || xs().exists(p)
case Empty => false case Empty => false
}
def foldRight[B](z: => B)(f: (A, => B) => B): B = this match { def foldRight[B](z: => B)(f: (A, => B) => B): B = this match
case Cons(x, xs) => f(x(), xs().foldRight(z)(f)) case Cons(x, xs) => f(x(), xs().foldRight(z)(f))
case Empty => z case Empty => z
}
}
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]
object Stream { // companion object object LazyList: // companion object
def fibs: Stream[Int] = { def fibs: LazyList[Int] =
// tip: write a recursive def here and call it with some start values // tip: write a recursive def here and call it with some start values
??? ???
}
def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] = ??? def unfold[A, S](z: S)(f: S => Option[(A, S)]): LazyList[A] = ???
def fibsViaUnfold: Stream[Int] = ??? def fibsViaUnfold: LazyList[Int] = ???
// Methods shown during Lecture // Methods shown during Lecture
def cons[A](h: => A, t: => Stream[A]): Stream[A] = { def cons[A](h: => A, t: => LazyList[A]): LazyList[A] =
lazy val head = h lazy val head = h
lazy val tail = t lazy val tail = t
Cons(() => head, () => tail) Cons(() => head, () => tail)
}
def empty[A]: Stream[A] = Empty def empty[A]: LazyList[A] = Empty
def apply[A](as: A*): Stream[A] = def apply[A](as: A*): LazyList[A] =
if (as.isEmpty) empty if as.isEmpty then empty
else cons(as.head, apply(as.tail: _*)) else cons(as.head, apply(as.tail: _*))
val ones: Stream[Int] = cons(1, ones) val ones: LazyList[Int] = cons(1, ones)
}