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/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/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..64763e2 100644 --- a/src/main/scala/fpspeedrun/Ratio.scala +++ b/src/main/scala/fpspeedrun/Ratio.scala @@ -1,10 +1,54 @@ package fpspeedrun -import syntax.eq._ +import fpspeedrun.Ord.Compare._ -final case class Ratio(num: Int, den: Int) +final case class Ratio(numer: Int, denom: Int) object Ratio { - implicit val eq: Eq[Ratio] = (x, y) => x.num.toLong * y.den === x.den.toLong * y.num -} + implicit val ordRatio: Ord[Ratio] = new Ord[Ratio] { + 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 + 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/compare.scala b/src/main/scala/fpspeedrun/syntax/compare.scala new file mode 100644 index 0000000..64b346e --- /dev/null +++ b/src/main/scala/fpspeedrun/syntax/compare.scala @@ -0,0 +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) + + 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 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