Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@phill101 Hometask 1, 2 (Ord, Monoid) #9

Open
wants to merge 3 commits into
base: run2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 3 additions & 23 deletions src/main/scala/fpspeedrun/Eq.scala
Original file line number Diff line number Diff line change
@@ -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 }
}
}
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)
}
}
29 changes: 22 additions & 7 deletions src/main/scala/fpspeedrun/Ord.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
52 changes: 48 additions & 4 deletions src/main/scala/fpspeedrun/Ratio.scala
Original file line number Diff line number Diff line change
@@ -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)
}
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)
}
21 changes: 21 additions & 0 deletions src/main/scala/fpspeedrun/syntax/compare.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
}
10 changes: 10 additions & 0 deletions src/main/scala/fpspeedrun/syntax/semigroup.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}