Skip to content

Instantly share code, notes, and snippets.

@yasuabe
Last active December 15, 2017 21:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yasuabe/c56140e53411bda6b137f84bc4ef38be to your computer and use it in GitHub Desktop.
Save yasuabe/c56140e53411bda6b137f84bc4ef38be to your computer and use it in GitHub Desktop.
ch15 mixed concurrencies - ch16 abstraction finally
package fp_tdd
import cats.Apply
import cats.syntax.apply._
import org.scalacheck.Prop.forAll
import org.scalacheck.{Arbitrary, Gen, Properties}
object Chapter16 extends Properties("ch16") {
// ======== TODO ========
// ======== DONE ========
// Bank.reduce(Money)
// $5 + $5 = $10
// Reduce(Bank, String)
// Reduce Money with conversion
// $5 + 10 CHF = $10 if rate is 2:1
// Sum.plus
// Expression.times
// Return Money from $5 + $5
// product code -------------------------------------------------------------------
trait Expression {
def *(multiplier: Int): Expression
def +(addend: Expression): Expression
def reduce(bank: Bank, to: String): Money
}
class Money(private[Chapter16] val amount: Int, val currency: String) extends Expression {
def *(multiplier: Int): Money = Money(amount * multiplier, currency)
def +(addend: Expression): Expression = Sum(this, addend)
def reduce(bank: Bank, to: String): Money =
Money(amount / bank.rate(currency -> to), to)
override def equals(a: Any): Boolean = a match { case another: Money =>
another.amount == amount &&
another.currency == currency
}
}
case class Sum(augend: Expression, append: Expression) extends Expression {
def *(multiplier: Int): Expression = Sum(augend * multiplier, append * multiplier)
def +(addend: Expression): Expression = Sum(this, addend)
def reduce(bank: Bank, to: String): Money = {
val amount = augend.reduce(bank, to).amount + append.reduce(bank, to).amount
Money(amount, to)
}
}
object Money {
def apply(amount: Int, currency: String) = new Money(amount, currency)
def dollar(amount: Int): Money = Money(amount, "USD")
def franc(amount: Int): Money = Money(amount, "CHF")
}
type CurrencyPair = (String, String)
class Bank(private val rates: Map[CurrencyPair, Int] = Map.empty) {
def reduce(source: Expression, to: String): Money = source.reduce(this, to)
def addRate(fromTo: CurrencyPair, rate: Int): Bank = new Bank(rates + (fromTo -> rate))
def rate(fromTo: CurrencyPair): Int = rates.getOrElse(fromTo, 1)
}
// test code --------------------------------------------------------------------
import Money.{dollar, franc}
implicit val intGen: Gen[Int] = Gen.chooseNum(-1000000, 1000000)
implicit val consGen: Gen[Int => Money] = Gen.oneOf(dollar(_:Int): Money, franc(_:Int): Money)
val rateValueGen: Gen[Int] = Gen.chooseNum(1, 10)
val currencyGen: Gen[String] = Gen.oneOf("USD", "CHF")
def double[G[_]: Apply, A](ga: G[A]): G[(A, A)] = (ga, ga).mapN((_, _))
def triple[G[_]: Apply, A](ga: G[A]): G[(A, A, A)] = (ga, ga, ga).mapN((_, _, _))
implicit val genApply: Apply[Gen] = new Apply[Gen] {
override def ap[A, B](gf: Gen[A => B])(ga: Gen[A]): Gen[B] = gf flatMap ga.map
override def map[A, B](ga: Gen[A])(f: A => B): Gen[B] = ga map f
}
implicit val moneyGen: Gen[Money] = (consGen, intGen).mapN(_(_))
implicit val moneyConsGen: Gen[Int => Money] = currencyGen map constructor
implicit val arbCons: Arbitrary[Int => Money] = Arbitrary(moneyConsGen)
implicit val arbMoney: Arbitrary[Money] = Arbitrary(moneyGen)
def constructor(currency: String)(n: Int): Money = currency match {
case "USD" => dollar(n)
case "CHF" => franc(n)
}
implicit class MoneyTestOp(m: Money) {
def equiv(e: Expression): Boolean = m == new Bank().reduce(e, m.currency)
}
def reduce(m: Money): Money = new Bank().reduce(m, m.currency)
def compair(b: Bank, currency: String)(e1: Expression, e2: Expression): Boolean =
b.reduce(e1, currency) == b.reduce(e2, currency)
implicit class CompareExpression(e1: Expression)(implicit p: (Expression, Expression) => Boolean) {
def ====(e2: Expression): Boolean = p(e1, e2)
}
def bank(rate: ((String, String), Int)): Bank = new Bank().addRate(rate._1, rate._2)
def mapPair[A, B](a1: A, a2: A)(f: A => B): (B, B) = (f(a1), f(a2))
def unique2[A](gen: Gen[A]): Gen[(A, A)] = double(gen).suchThat { case (a, b) => a != b }
val unique3: Gen[(Money, Money, Money)] =
triple(moneyGen).suchThat { case (a, b, c) => a != b && b != c && c != a }
val rateGen: Gen[((String, String), Int)] = {
val g1 = currencyGen.map(c => ((c, c), 1))
val g2 = (unique2(currencyGen), rateValueGen).mapN((_, _))
Gen.frequency((1, g1), (2, g2))
}
// properties -------------------------------------------------------------------
property("m(0) * x = m(0)") = forAll { (x: Int, m: Int => Money) =>
m(0) * x == m(0)
}
property("m(1) * x = m(x)") = forAll { (x: Int, m: Int => Money) =>
m(1) * x == m(x)
}
property("m(a) * b = m(b) * a") = forAll { (a: Int, b: Int, m: Int => Money) =>
m(a) * b == m(b) * a
}
property("(m(a) * b) * c = m(a) * (b * c)") = forAll { (a: Int, b: Int, c: Int, m: Int => Money) =>
(m(a) * b) * c == m(a) * (b * c)
}
property("m(a) == m(b) <=> a == b") = forAll(double(intGen), moneyConsGen) { case ((a, b), m) =>
(m(a) == m(b)) == (a == b)
}
property("m(a) == m(b) <=> m(a) equals m(b)") = forAll(double(moneyGen)) { case (a: Money, b: Money) =>
a.equals(b) == (a == b)
}
property("equals is reflexive") = forAll { (a: Money) =>
a equals a
}
property("equals is symmetric") = forAll(double(intGen)) { case (a, b) =>
(a equals b) == (b equals a)
}
property("equals is transitive") = forAll( for {
oneOf3 <- unique3.map { case (a, b, c)=> Gen.oneOf(a, b, c) }
x <- oneOf3
y <- oneOf3.suchThat(_ equals x)
z <- oneOf3.retryUntil(_ equals y)
} yield (x, z)) { case ((x, z)) => x equals z }
property("(c1==c2) == (m1.currency==m2.currency)") =
forAll(currencyGen, currencyGen, intGen) { (c1, c2, n) =>
val (m1, m2) = mapPair(c1, c2)((currency: String) => constructor(currency)(n))
(c1 == c2) == (m1.currency == m2.currency)
}
property("money(a + b) == money(a) + money(b)") =
forAll(intGen, intGen, moneyConsGen) { (a: Int, b: Int, money: Int => Money) =>
money(a + b) equiv money(a) + money(b)
}
property("m == reduce(m, m.currency) where m = money(n)") =
forAll(intGen, moneyConsGen) { (n: Int, money: Int => Money) =>
reduce(money(n)) == money(n)
}
property("reduce(m1(amount)) == m2(amount / rate)") =
forAll(intGen, rateGen) { case (amount, rate@((src, dst), r)) =>
val (m1, m2) = mapPair(src, dst)(constructor)
val reduce = (e: Expression) => bank(rate).reduce(e, dst)
reduce(m1(amount)) == m2(amount / r)
}
property("identity rate") = forAll(currencyGen) { c: String =>
new Bank().rate(c -> c) == 1
}
property("reduce(c1(n1) + c2(n2)) == c2(n1 / r + n2)") =
forAll(intGen, intGen, rateGen) { case (n1, n2, rate@((src, dst), r)) =>
val (c1, c2) = mapPair(src, dst)(constructor)
val reduce = (e: Expression) => bank(rate).reduce(e, dst)
reduce(c1(n1) + c2(n2)) == c2(n1 / r + n2)
}
property("reduce(x + y) equiv reduce(x) + y") =
forAll(intGen, intGen, rateGen) { case (n1, n2, ((src, dst), r)) =>
val reduce = (e: Expression) => new Bank().addRate(src -> dst, r).reduce(e, dst)
val x = constructor(src)(n1)
val y = constructor(dst)(n2)
reduce(x + y) equiv reduce(x) + y
}
property("(x + y) + z = x + (y + z)") =
forAll(triple(moneyGen), rateGen) { case ((x, y, z), ((src, dst), r)) =>
implicit val _ = compair(new Bank().addRate(src -> dst, r), dst) _
(x + y) + z ==== x + (y + z)
}
property("x * m + y * m = (x + y) * m") =
forAll(double(moneyGen), intGen, rateGen) { case ((x, y), m, ((src, dst), r)) =>
implicit val _ = compair(new Bank().addRate(src -> dst, r), dst) _
x * m + y * m ==== (x + y) * m
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment