Skip to content

Commit 493e89a

Browse files
committed
Monoid task implementation.
1 parent 64e7eca commit 493e89a

File tree

5 files changed

+163
-0
lines changed

5 files changed

+163
-0
lines changed

src/main/scala/fpspeedrun/Iso.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package fpspeedrun
2+
3+
trait Iso[T, U] {
4+
def wrap(x: T): U
5+
def unwrap(x: U): T
6+
}
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package fpspeedrun
2+
3+
import scala.language.higherKinds
4+
import syntax.semigroup._
5+
6+
trait Monoid[T] extends SemiGroup[T] {
7+
def empty: T
8+
}
9+
10+
object Monoid {
11+
def apply[T](implicit i: Monoid[T]) = i
12+
13+
object Laws {
14+
def identity[T : Monoid](x: T): Boolean = {
15+
(Monoid[T].empty |+| x) == x &&
16+
(x |+| Monoid[T].empty) == x
17+
}
18+
19+
def associativity[T : Monoid](x: T, y: T, z: T): Boolean = {
20+
((x |+| y) |+| z) == (x |+| (y |+| z))
21+
}
22+
}
23+
24+
/**
25+
* Функция, похожая на SemiGroup#combineList, но для Monoid.
26+
* В отличие от предыдущей, возвращает не Option[T], а сам T.
27+
* Для пустого списка, возвращает "единицу" в понятиях моноида.
28+
*/
29+
def foldList[T : Monoid](list: List[T]): T = {
30+
list.foldLeft(Monoid[T].empty) {
31+
case (sum, next) => sum |+| next
32+
}
33+
}
34+
35+
/**
36+
* #foldList, но с крутым синтаксисом и возможностью через параметр типа передавать
37+
* желаемое поведение.
38+
* Паттерн для синтаксиса называется: partial type application
39+
*/
40+
def foldListVia[U[_]] = new FoldListVia[U]
41+
class FoldListVia[U[_]] {
42+
def apply[T](list: List[T])(implicit iso: Iso[T, U[T]], monoid: Monoid[U[T]]): T = {
43+
val r = list.foldLeft(monoid.empty) {
44+
(acc, next) => acc |+| iso.wrap(next)
45+
}
46+
iso.unwrap(r)
47+
}
48+
}
49+
50+
// monoid instances
51+
implicit val defaultMonoidInt: Monoid[Int] = new Monoid[Int] {
52+
override def empty: Int = 0
53+
override def combine(x: Int, y: Int): Int = x+y
54+
}
55+
implicit val sumMonoidInt: Monoid[Sum[Int]] = new Monoid[Sum[Int]] {
56+
override def empty: Sum[Int] = Sum(0)
57+
override def combine(x: Sum[Int], y: Sum[Int]): Sum[Int] = Sum(x.x + y.x)
58+
}
59+
implicit val prodMonoidInt: Monoid[Prod[Int]] = new Monoid[Prod[Int]] {
60+
override def empty: Prod[Int] = Prod(1)
61+
override def combine(x: Prod[Int], y: Prod[Int]): Prod[Int] = Prod(x.x * y.x)
62+
}
63+
}

src/main/scala/fpspeedrun/Ratio.scala

+36
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,40 @@ object Ratio {
1515
else if (x.numer * y.denom > y.numer * x.denom) GT
1616
else LT
1717
}
18+
19+
// semigroup instances
20+
implicit val combineRatio: SemiGroup[Sum[Ratio]] =
21+
(x: Sum[Ratio], y: Sum[Ratio]) => Sum(sum(x.x, y.x))
22+
implicit val mulCombineRatio: SemiGroup[Prod[Ratio]] =
23+
(x: Prod[Ratio], y: Prod[Ratio]) => Prod(mul(x.x, y.x))
24+
25+
// monoid instances
26+
implicit val defaultMonoidRatio: Monoid[Ratio] = new Monoid[Ratio] {
27+
override def empty: Ratio = Ratio(0, 1)
28+
override def combine(x: Ratio, y: Ratio): Ratio = sum(x, y)
29+
}
30+
implicit val sumMonoidRatio: Monoid[Sum[Ratio]] = new Monoid[Sum[Ratio]] {
31+
override def empty: Sum[Ratio] = Sum(Ratio(0, 1))
32+
override def combine(x: Sum[Ratio], y: Sum[Ratio]): Sum[Ratio] = Sum(sum(x.x, y.x))
33+
}
34+
implicit val mulMonoidRatio: Monoid[Prod[Ratio]] = new Monoid[Prod[Ratio]] {
35+
override def empty: Prod[Ratio] = Prod(Ratio(1, 1))
36+
override def combine(x: Prod[Ratio], y: Prod[Ratio]): Prod[Ratio] = Prod(mul(x.x, y.x))
37+
}
38+
39+
def sum(x: Ratio, y: Ratio): Ratio = {
40+
val num = x.numer * y.denom + x.denom * y.numer
41+
val denom = x.denom * y.denom
42+
lazy val g = gcd(num, denom)
43+
Ratio(num / g, denom / g)
44+
}
45+
46+
def mul(x: Ratio, y: Ratio): Ratio = {
47+
val num = x.numer * y.numer
48+
val denom = x.denom * y.denom
49+
lazy val g = gcd(num, denom)
50+
Ratio(num/g, denom/g)
51+
}
52+
53+
def gcd(a: Int, b: Int): Int = if (b==0) a else gcd(b, a%b)
1854
}
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package fpspeedrun
2+
import syntax.semigroup._
3+
4+
trait SemiGroup[T] {
5+
def combine(x: T, y: T): T
6+
}
7+
8+
object SemiGroup {
9+
object Laws {
10+
def associativity[T : SemiGroup](x: T, y: T, z: T): Boolean = {
11+
((x |+| y) |+| z) == (x |+| (y |+| z))
12+
}
13+
}
14+
15+
implicit val combineString: SemiGroup[String] = (x: String, y: String) => x + y
16+
17+
18+
def combineList[T : SemiGroup](list: List[T]): Option[T] = {
19+
list.reduceOption(_ |+| _)
20+
}
21+
22+
def combineListVia[U[_]] = new CombineListVia[U]
23+
24+
// partial type application
25+
class CombineListVia[U[_]] {
26+
def apply[T](list: List[T])(implicit iso: Iso[T, U[T]], sg: SemiGroup[U[T]]): Option[T] =
27+
list.reduceOption((x, y) => iso.unwrap(iso.wrap(x) |+| iso.wrap(y)))
28+
}
29+
}
30+
31+
32+
final case class Sum[T](x: T) extends AnyVal
33+
final case class Prod[T](x: T) extends AnyVal
34+
35+
object Sum {
36+
implicit def sumIso[T]: Iso[T, Sum[T]] = new Iso[T, Sum[T]] {
37+
override def wrap(x: T): Sum[T] = Sum(x)
38+
override def unwrap(x: Sum[T]): T = x.x
39+
}
40+
implicit val combineInt: SemiGroup[Sum[Int]] =
41+
(x: Sum[Int], y: Sum[Int]) => Sum(x.x + y.x)
42+
}
43+
44+
object Prod {
45+
implicit def prodIso[T]: Iso[T, Prod[T]] = new Iso[T, Prod[T]] {
46+
override def wrap(x: T): Prod[T] = Prod(x)
47+
override def unwrap(x: Prod[T]): T = x.x
48+
}
49+
implicit val mulCombineInt: SemiGroup[Prod[Int]] =
50+
(x: Prod[Int], y: Prod[Int]) => Prod(x.x * y.x)
51+
}

src/main/scala/fpspeedrun/syntax.scala

+7
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,11 @@ object syntax {
2424
}
2525
}
2626
}
27+
28+
object semigroup {
29+
implicit class SemiGroupOps[T](val x: T) extends AnyVal {
30+
def combine(y: T)(implicit sg: SemiGroup[T]): T = sg.combine(x, y)
31+
def |+|(y: T)(implicit sg: SemiGroup[T]): T = combine(y)
32+
}
33+
}
2734
}

0 commit comments

Comments
 (0)