From 32d699bf19fbc768a2f9276be53b4c53d7dff56e Mon Sep 17 00:00:00 2001 From: Nikita Filimonov Date: Sat, 16 Jun 2018 17:46:59 +0500 Subject: [PATCH 1/3] FirstImpl --- src/main/scala/fpspeedrun/Eq.scala | 26 ++--------------- src/main/scala/fpspeedrun/Ord.scala | 29 ++++++++++++++----- src/main/scala/fpspeedrun/Ratio.scala | 19 ++++++++++++ .../scala/fpspeedrun/syntax/compare.scala | 7 +++++ 4 files changed, 51 insertions(+), 30 deletions(-) create mode 100644 src/main/scala/fpspeedrun/syntax/compare.scala diff --git a/src/main/scala/fpspeedrun/Eq.scala b/src/main/scala/fpspeedrun/Eq.scala index 9dfc567..2960e77 100644 --- a/src/main/scala/fpspeedrun/Eq.scala +++ b/src/main/scala/fpspeedrun/Eq.scala @@ -1,33 +1,13 @@ package fpspeedrun import syntax.eq._ -import scala.annotation.tailrec - trait Eq[T] { def ===(x: T, y: T): Boolean } object Eq { - def fromEquals[T]: Eq[T] = _ == _ - - implicit val intEq: Eq[Int] = fromEquals - implicit val longEq: Eq[Long] = fromEquals - - /** простой вариант */ - implicit def vectorEq[A: Eq]: Eq[Vector[A]] = - (xs, ys) => xs.view.zip(ys).forall { case (x, y) => x === y } - - - /** я у мамы оптимизатор */ - implicit def listEq[A: Eq]: Eq[List[A]] = { - @tailrec def go(xs: List[A], ys: List[A]): Boolean = - xs match { - case Nil => ys.isEmpty - case x :: xt => ys match { - case Nil => false - case y :: yt => x === y && go(xt, yt) - } - } - go + implicit def compareLists[T](implicit eqq: Eq[T]): Eq[List[T]] = { + (x: List[T], y: List[T]) => x.nonEmpty && x.lengthCompare(y.length) == 0 && + x.zip(y).forall { case (f, s) => f === s } } } diff --git a/src/main/scala/fpspeedrun/Ord.scala b/src/main/scala/fpspeedrun/Ord.scala index 231eec3..45ba8be 100644 --- a/src/main/scala/fpspeedrun/Ord.scala +++ b/src/main/scala/fpspeedrun/Ord.scala @@ -1,15 +1,30 @@ package fpspeedrun + import fpspeedrun.Ord.Compare +import fpspeedrun.Ord.Compare._ -trait Ord[T] extends Eq[T]{ +trait Ord[T] extends Eq[T] { def compare(x: T, y: T): Compare } -object Ord{ +object Ord { sealed trait Compare - object Compare{ - case object LT //less than - case object EQ //equals to - case object GT //greater than + object Compare { + case object LT extends Compare // less than + case object EQ extends Compare // equals to + case object GT extends Compare // greater than } -} + + implicit def compareLists[T](implicit ord: Ord[T]): Ord[List[T]] = + new Ord[List[T]] { + override def compare(x: List[T], y: List[T]): Compare = + x.lengthCompare(y.length) match { + case neg if neg < 0 => LT + case zer if zer == 0 => EQ + case _ => GT + } + + import syntax.eq._ + override def ===(x: List[T], y: List[T]): Boolean = x === y + } +} \ No newline at end of file diff --git a/src/main/scala/fpspeedrun/Ratio.scala b/src/main/scala/fpspeedrun/Ratio.scala index 4587510..0d5a020 100644 --- a/src/main/scala/fpspeedrun/Ratio.scala +++ b/src/main/scala/fpspeedrun/Ratio.scala @@ -1,5 +1,6 @@ package fpspeedrun +<<<<<<< HEAD import syntax.eq._ final case class Ratio(num: Int, den: Int) @@ -7,4 +8,22 @@ final case class Ratio(num: Int, den: Int) object Ratio { implicit val eq: Eq[Ratio] = (x, y) => x.num.toLong * y.den === x.den.toLong * y.num } +======= +import fpspeedrun.Ord.Compare._ +>>>>>>> FirstImpl +final case class Ratio(numer: Int, denom: Int) + +object Ratio { + implicit val eqRatio: Eq[Ratio] = + (x: Ratio, y: Ratio) => x.numer * y.denom == y.numer * x.denom + + implicit val ordRatio: Ord[Ratio] = new Ord[Ratio] { + override def ===(x: Ratio, y: Ratio): Boolean = eqRatio.===(x, y) + + override def compare(x: Ratio, y: Ratio): Ord.Compare = + if (===(x, y)) EQ + else if (x.numer * y.denom > y.numer * x.denom) GT + else LT + } +} \ No newline at end of file diff --git a/src/main/scala/fpspeedrun/syntax/compare.scala b/src/main/scala/fpspeedrun/syntax/compare.scala new file mode 100644 index 0000000..8227d54 --- /dev/null +++ b/src/main/scala/fpspeedrun/syntax/compare.scala @@ -0,0 +1,7 @@ +package fpspeedrun.syntax + +object compare { + implicit class CompareOps[T](val x: T) extends AnyVal { + def compare(y: T)(implicit ord: Ord[T]): Ord.Compare = ord.compare(x, y) + } + } \ No newline at end of file From 0c4ebaa62a91e8860b595e4e1a3ca523f3fb55cd Mon Sep 17 00:00:00 2001 From: Nikita Filimonov Date: Sat, 16 Jun 2018 18:48:14 +0500 Subject: [PATCH 2/3] Some little improvements --- src/main/scala/fpspeedrun/Ratio.scala | 15 ++------------ .../scala/fpspeedrun/syntax/compare.scala | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/scala/fpspeedrun/Ratio.scala b/src/main/scala/fpspeedrun/Ratio.scala index 0d5a020..b5d1bcc 100644 --- a/src/main/scala/fpspeedrun/Ratio.scala +++ b/src/main/scala/fpspeedrun/Ratio.scala @@ -1,25 +1,14 @@ package fpspeedrun -<<<<<<< HEAD -import syntax.eq._ - -final case class Ratio(num: Int, den: Int) - -object Ratio { - implicit val eq: Eq[Ratio] = (x, y) => x.num.toLong * y.den === x.den.toLong * y.num -} -======= import fpspeedrun.Ord.Compare._ ->>>>>>> FirstImpl final case class Ratio(numer: Int, denom: Int) object Ratio { - implicit val eqRatio: Eq[Ratio] = - (x: Ratio, y: Ratio) => x.numer * y.denom == y.numer * x.denom implicit val ordRatio: Ord[Ratio] = new Ord[Ratio] { - override def ===(x: Ratio, y: Ratio): Boolean = eqRatio.===(x, y) + override def ===(x: Ratio, y: Ratio): Boolean = + x.numer * y.denom == y.numer * x.denom override def compare(x: Ratio, y: Ratio): Ord.Compare = if (===(x, y)) EQ diff --git a/src/main/scala/fpspeedrun/syntax/compare.scala b/src/main/scala/fpspeedrun/syntax/compare.scala index 8227d54..64b346e 100644 --- a/src/main/scala/fpspeedrun/syntax/compare.scala +++ b/src/main/scala/fpspeedrun/syntax/compare.scala @@ -1,7 +1,21 @@ package fpspeedrun.syntax +import fpspeedrun.Ord +import fpspeedrun.Ord.Compare.{EQ, GT, LT} + object compare { - implicit class CompareOps[T](val x: T) extends AnyVal { - def compare(y: T)(implicit ord: Ord[T]): Ord.Compare = ord.compare(x, y) + implicit class CompareOps[T](val x: T) extends AnyVal { + def compare(y: T)(implicit ord: Ord[T]): Ord.Compare = ord.compare(x, y) + + def >(y: T)(implicit ord: Ord[T]): Boolean = compare(y) == GT + def <(y: T)(implicit ord: Ord[T]): Boolean = compare(y) == LT + def >=(y: T)(implicit ord: Ord[T]): Boolean = { + val c = compare(y) + c == GT || c == EQ + } + def <=(y: T)(implicit ord: Ord[T]): Boolean = { + val c = compare(y) + c == LT || c == EQ } - } \ No newline at end of file + } +} \ No newline at end of file From cedf38180572b82c4c09f388930eebda57215f91 Mon Sep 17 00:00:00 2001 From: Nikita Filimonov Date: Wed, 20 Jun 2018 00:26:59 +0500 Subject: [PATCH 3/3] Monoid task implementation. --- src/main/scala/fpspeedrun/Iso.scala | 6 ++ src/main/scala/fpspeedrun/Monoid.scala | 63 +++++++++++++++++++ src/main/scala/fpspeedrun/Ratio.scala | 36 +++++++++++ src/main/scala/fpspeedrun/SemiGroup.scala | 51 +++++++++++++++ .../scala/fpspeedrun/syntax/semigroup.scala | 10 +++ 5 files changed, 166 insertions(+) create mode 100644 src/main/scala/fpspeedrun/Iso.scala create mode 100644 src/main/scala/fpspeedrun/Monoid.scala create mode 100644 src/main/scala/fpspeedrun/SemiGroup.scala create mode 100644 src/main/scala/fpspeedrun/syntax/semigroup.scala diff --git a/src/main/scala/fpspeedrun/Iso.scala b/src/main/scala/fpspeedrun/Iso.scala new file mode 100644 index 0000000..c683347 --- /dev/null +++ b/src/main/scala/fpspeedrun/Iso.scala @@ -0,0 +1,6 @@ +package fpspeedrun + +trait Iso[T, U] { + def wrap(x: T): U + def unwrap(x: U): T +} diff --git a/src/main/scala/fpspeedrun/Monoid.scala b/src/main/scala/fpspeedrun/Monoid.scala new file mode 100644 index 0000000..76b0a94 --- /dev/null +++ b/src/main/scala/fpspeedrun/Monoid.scala @@ -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) + } +} \ No newline at end of file diff --git a/src/main/scala/fpspeedrun/Ratio.scala b/src/main/scala/fpspeedrun/Ratio.scala index b5d1bcc..64763e2 100644 --- a/src/main/scala/fpspeedrun/Ratio.scala +++ b/src/main/scala/fpspeedrun/Ratio.scala @@ -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) } \ No newline at end of file diff --git a/src/main/scala/fpspeedrun/SemiGroup.scala b/src/main/scala/fpspeedrun/SemiGroup.scala new file mode 100644 index 0000000..ffddc3b --- /dev/null +++ b/src/main/scala/fpspeedrun/SemiGroup.scala @@ -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) +} \ No newline at end of file diff --git a/src/main/scala/fpspeedrun/syntax/semigroup.scala b/src/main/scala/fpspeedrun/syntax/semigroup.scala new file mode 100644 index 0000000..b79ff03 --- /dev/null +++ b/src/main/scala/fpspeedrun/syntax/semigroup.scala @@ -0,0 +1,10 @@ +package fpspeedrun.syntax + +import fpspeedrun.SemiGroup + +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) + } + } \ No newline at end of file