diff --git a/src/main/scala/fpinscalalib/FPinScalaLibrary.scala b/src/main/scala/fpinscalalib/FPinScalaLibrary.scala index 13127cf..c1b58c9 100644 --- a/src/main/scala/fpinscalalib/FPinScalaLibrary.scala +++ b/src/main/scala/fpinscalalib/FPinScalaLibrary.scala @@ -25,7 +25,8 @@ object FPinScalaLibrary extends Library { FunctionalStateSection, FunctionalParallelismSection, PropertyBasedTestingSection, - ParserCombinatorsSection + ParserCombinatorsSection, + MonoidsSection ) override def logoPath = "fp_in_scala" diff --git a/src/main/scala/fpinscalalib/MonoidsSection.scala b/src/main/scala/fpinscalalib/MonoidsSection.scala new file mode 100644 index 0000000..bcc8df3 --- /dev/null +++ b/src/main/scala/fpinscalalib/MonoidsSection.scala @@ -0,0 +1,117 @@ +package fpinscalalib + +import fpinscalalib.customlib.monoids.Monoid +import org.scalatest.Matchers +import org.scalatest.FlatSpec + +object MonoidsSection extends FlatSpec + with Matchers + with org.scalaexercises.definitions.Section { + + /** + * = Functional programming in Scala = + * + * The following set of sections represent the exercises contained in the book "Functional Programming in Scala", + * written by Paul Chiusano and RĂșnar Bjarnason and published by Manning. This content library is meant to be used + * in tandem with the book. We use the same numeration for the exercises for you to follow them. + * + * For more information about "Functional Programming in Scala" please visit its + * official website. + * + * = What is a monoid? = + * + * Exercise 10.1 + * + * Give Monoid instances for integer addition and multiplication as well as the Boolean operators + * + * + * Let's implement Monoid instances for integer addition and multiplication as well as the Boolean operators, taking this representation of `Monoid`: + * + * {{{ + * trait Monoid[A] { + * def op(a1: A, a2: A): A + * def zero: A + * } + * }}} + **/ + def monoidInstancesAssert( + res0: Int, + res1: Int, + res2: Boolean, + res3: Boolean + ): Unit = { + + val intAddition: Monoid[Int] = new Monoid[Int] { + def op(x: Int, y: Int): Int = x + y + def zero: Int = res0 + } + + val intMultipication: Monoid[Int] = new Monoid[Int] { + def op(x: Int, y: Int): Int = x * y + def zero: Int = res1 + } + + val booleanOr: Monoid[Boolean] = new Monoid[Boolean] { + def op(x: Boolean, y: Boolean): Boolean = x || y + def zero: Boolean = res2 + } + + def booleanAnd: Monoid[Boolean] = new Monoid[Boolean] { + def op(x: Boolean, y: Boolean): Boolean = x && y + def zero: Boolean = res3 + } + + intAddition.op(intAddition.zero, 5) shouldBe 5 + intAddition.op(5, intAddition.zero) shouldBe 5 + + intMultipication.op(intMultipication.zero, 5) shouldBe 5 + intMultipication.op(5, intMultipication.zero) shouldBe 5 + + booleanOr.op(booleanOr.zero, true) shouldBe true + booleanOr.op(true, booleanOr.zero) shouldBe true + booleanOr.op(booleanOr.zero, false) shouldBe false + booleanOr.op(false, booleanOr.zero) shouldBe false + + booleanAnd.op(booleanAnd.zero, true) shouldBe true + booleanAnd.op(true, booleanAnd.zero) shouldBe true + booleanAnd.op(booleanAnd.zero, false) shouldBe false + booleanAnd.op(false, booleanAnd.zero) shouldBe false + } + + /** + * Exercise 10.2 + * + * Let's give a Monoid instance for combining Option values + */ + def optionMonoidAssert(res0: Option[Int], res1: Option[Int]): Unit = { + def optionMonoid[A]: Monoid[Option[A]] = new Monoid[Option[A]] { + def op(x: Option[A], y: Option[A]): Option[A] = x.orElse(y) + def zero: Option[A] = None + } + + optionMonoid[Int].op(Option(2), Option(3)) shouldBe res0 + optionMonoid[Int].op(Option(2), optionMonoid[Int].zero) shouldBe res1 + } + + /** + * Exercise 10.3 + * + * Let's write a monoid for endofunctions (= functions having the same argument and return type) + */ + def endoMonoidAssert( + res0: Int, + res1: Int, + res2: Int, + res3: Int + ): Unit = { + def endoMonoid[A]: Monoid[A => A] = new Monoid[A => A] { + def op(f: A => A, g: A => A): A => A = x => f(g(x)) + def zero: A => A = x => x + } + + endoMonoid[Int].op(_ + 1, _ * 2)(10) shouldBe res0 + endoMonoid[Int].op(_ * 2, _ + 1)(10) shouldBe res1 + endoMonoid[Int].op(_ + 1, endoMonoid[Int].zero)(10) shouldBe res2 + endoMonoid[Int].op(endoMonoid[Int].zero, endoMonoid[Int].zero)(10) shouldBe res3 + } +} diff --git a/src/main/scala/fpinscalalib/customlib/monoids/Monoid.scala b/src/main/scala/fpinscalalib/customlib/monoids/Monoid.scala new file mode 100644 index 0000000..03ceaf2 --- /dev/null +++ b/src/main/scala/fpinscalalib/customlib/monoids/Monoid.scala @@ -0,0 +1,6 @@ +package fpinscalalib.customlib.monoids + +trait Monoid[A] { + def op(a1: A, a2: A): A + def zero: A +} diff --git a/src/test/scala/fpinscalalib/MonoidsSpec.scala b/src/test/scala/fpinscalalib/MonoidsSpec.scala new file mode 100644 index 0000000..8044076 --- /dev/null +++ b/src/test/scala/fpinscalalib/MonoidsSpec.scala @@ -0,0 +1,12 @@ +package fpinscalalib + +import org.scalatest.refspec.RefSpec +import org.scalatest.prop.Checkers + +class MonoidsSpec extends RefSpec with Checkers { + def `monoid instances asserts` = { + MonoidsSection.monoidInstancesAssert(0, 1, false, true) + MonoidsSection.optionMonoidAssert(Option(2), Option(2)) + MonoidsSection.endoMonoidAssert(21, 22, 11, 10) + } +}