Skip to content

Commit

Permalink
Monoid task implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Phill101 committed Jun 19, 2018
1 parent 64e7eca commit 493e89a
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/main/scala/fpspeedrun/Iso.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package fpspeedrun

trait Iso[T, U] {
def wrap(x: T): U
def unwrap(x: U): T
}
63 changes: 63 additions & 0 deletions src/main/scala/fpspeedrun/Monoid.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package fpspeedrun

import scala.language.higherKinds
import syntax.semigroup._

trait Monoid[T] extends SemiGroup[T] {
def empty: T
}

object Monoid {
def apply[T](implicit i: Monoid[T]) = i

object Laws {
def identity[T : Monoid](x: T): Boolean = {
(Monoid[T].empty |+| x) == x &&
(x |+| Monoid[T].empty) == x
}

def associativity[T : Monoid](x: T, y: T, z: T): Boolean = {
((x |+| y) |+| z) == (x |+| (y |+| z))
}
}

/**
* Функция, похожая на SemiGroup#combineList, но для Monoid.
* В отличие от предыдущей, возвращает не Option[T], а сам T.
* Для пустого списка, возвращает "единицу" в понятиях моноида.
*/
def foldList[T : Monoid](list: List[T]): T = {
list.foldLeft(Monoid[T].empty) {
case (sum, next) => sum |+| next
}
}

/**
* #foldList, но с крутым синтаксисом и возможностью через параметр типа передавать
* желаемое поведение.
* Паттерн для синтаксиса называется: partial type application
*/
def foldListVia[U[_]] = new FoldListVia[U]
class FoldListVia[U[_]] {
def apply[T](list: List[T])(implicit iso: Iso[T, U[T]], monoid: Monoid[U[T]]): T = {
val r = list.foldLeft(monoid.empty) {
(acc, next) => acc |+| iso.wrap(next)
}
iso.unwrap(r)
}
}

// monoid instances
implicit val defaultMonoidInt: Monoid[Int] = new Monoid[Int] {
override def empty: Int = 0
override def combine(x: Int, y: Int): Int = x+y
}
implicit val sumMonoidInt: Monoid[Sum[Int]] = new Monoid[Sum[Int]] {
override def empty: Sum[Int] = Sum(0)
override def combine(x: Sum[Int], y: Sum[Int]): Sum[Int] = Sum(x.x + y.x)
}
implicit val prodMonoidInt: Monoid[Prod[Int]] = new Monoid[Prod[Int]] {
override def empty: Prod[Int] = Prod(1)
override def combine(x: Prod[Int], y: Prod[Int]): Prod[Int] = Prod(x.x * y.x)
}
}
36 changes: 36 additions & 0 deletions src/main/scala/fpspeedrun/Ratio.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,40 @@ object Ratio {
else if (x.numer * y.denom > y.numer * x.denom) GT
else LT
}

// semigroup instances
implicit val combineRatio: SemiGroup[Sum[Ratio]] =
(x: Sum[Ratio], y: Sum[Ratio]) => Sum(sum(x.x, y.x))
implicit val mulCombineRatio: SemiGroup[Prod[Ratio]] =
(x: Prod[Ratio], y: Prod[Ratio]) => Prod(mul(x.x, y.x))

// monoid instances
implicit val defaultMonoidRatio: Monoid[Ratio] = new Monoid[Ratio] {
override def empty: Ratio = Ratio(0, 1)
override def combine(x: Ratio, y: Ratio): Ratio = sum(x, y)
}
implicit val sumMonoidRatio: Monoid[Sum[Ratio]] = new Monoid[Sum[Ratio]] {
override def empty: Sum[Ratio] = Sum(Ratio(0, 1))
override def combine(x: Sum[Ratio], y: Sum[Ratio]): Sum[Ratio] = Sum(sum(x.x, y.x))
}
implicit val mulMonoidRatio: Monoid[Prod[Ratio]] = new Monoid[Prod[Ratio]] {
override def empty: Prod[Ratio] = Prod(Ratio(1, 1))
override def combine(x: Prod[Ratio], y: Prod[Ratio]): Prod[Ratio] = Prod(mul(x.x, y.x))
}

def sum(x: Ratio, y: Ratio): Ratio = {
val num = x.numer * y.denom + x.denom * y.numer
val denom = x.denom * y.denom
lazy val g = gcd(num, denom)
Ratio(num / g, denom / g)
}

def mul(x: Ratio, y: Ratio): Ratio = {
val num = x.numer * y.numer
val denom = x.denom * y.denom
lazy val g = gcd(num, denom)
Ratio(num/g, denom/g)
}

def gcd(a: Int, b: Int): Int = if (b==0) a else gcd(b, a%b)
}
51 changes: 51 additions & 0 deletions src/main/scala/fpspeedrun/SemiGroup.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package fpspeedrun
import syntax.semigroup._

trait SemiGroup[T] {
def combine(x: T, y: T): T
}

object SemiGroup {
object Laws {
def associativity[T : SemiGroup](x: T, y: T, z: T): Boolean = {
((x |+| y) |+| z) == (x |+| (y |+| z))
}
}

implicit val combineString: SemiGroup[String] = (x: String, y: String) => x + y


def combineList[T : SemiGroup](list: List[T]): Option[T] = {
list.reduceOption(_ |+| _)
}

def combineListVia[U[_]] = new CombineListVia[U]

// partial type application
class CombineListVia[U[_]] {
def apply[T](list: List[T])(implicit iso: Iso[T, U[T]], sg: SemiGroup[U[T]]): Option[T] =
list.reduceOption((x, y) => iso.unwrap(iso.wrap(x) |+| iso.wrap(y)))
}
}


final case class Sum[T](x: T) extends AnyVal
final case class Prod[T](x: T) extends AnyVal

object Sum {
implicit def sumIso[T]: Iso[T, Sum[T]] = new Iso[T, Sum[T]] {
override def wrap(x: T): Sum[T] = Sum(x)
override def unwrap(x: Sum[T]): T = x.x
}
implicit val combineInt: SemiGroup[Sum[Int]] =
(x: Sum[Int], y: Sum[Int]) => Sum(x.x + y.x)
}

object Prod {
implicit def prodIso[T]: Iso[T, Prod[T]] = new Iso[T, Prod[T]] {
override def wrap(x: T): Prod[T] = Prod(x)
override def unwrap(x: Prod[T]): T = x.x
}
implicit val mulCombineInt: SemiGroup[Prod[Int]] =
(x: Prod[Int], y: Prod[Int]) => Prod(x.x * y.x)
}
7 changes: 7 additions & 0 deletions src/main/scala/fpspeedrun/syntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,11 @@ object syntax {
}
}
}

object semigroup {
implicit class SemiGroupOps[T](val x: T) extends AnyVal {
def combine(y: T)(implicit sg: SemiGroup[T]): T = sg.combine(x, y)
def |+|(y: T)(implicit sg: SemiGroup[T]): T = combine(y)
}
}
}

0 comments on commit 493e89a

Please sign in to comment.