Skip to content

Instantly share code, notes, and snippets.

@yasuabe
Created December 15, 2017 18:11
Show Gist options
  • Save yasuabe/2e52a307624acfc350d6683b9b1b3ef8 to your computer and use it in GitHub Desktop.
Save yasuabe/2e52a307624acfc350d6683b9b1b3ef8 to your computer and use it in GitHub Desktop.
ch08 makin' objects
package fp_tdd
import cats.Apply
import cats.syntax.apply._
import org.scalacheck.Prop.forAll
import org.scalacheck.{Arbitrary, Gen, Properties}
object Chapter08 extends Properties("Ch08") {
// ======== TODO ========
// $5 + 10 CHF = $10 if rate is 2:1
// Money rounding?
// hashCode
// Equal null
// Equal object
// Dollar/Farnc duplication
// Common times
// Currency
// ======== DONE ========
// 1. $5 * 2 = $10
// 2. Dollar side effect
// 2. times -> `*`
// 3. Equality
// 5. 5CHF * 2 = 10CHF
// 6. Common equals
// 7. Compare Francs to Dollars
// 8. add `*` on Money
// product code -------------------------------------------------------------------
trait Money {
protected val amount: Int
def *(multiplier: Int): Money
override def equals(a: Any): Boolean =
a.asInstanceOf[Money].amount == amount && this.getClass == a.getClass
}
object Money {
def dollar(amount: Int): Money = Dollar(amount)
def franc(amount: Int): Money = Franc(amount)
}
class Dollar(protected val amount: Int) extends Money {
def *(multiplier: Int): Money = Dollar(amount * multiplier)
}
object Dollar { def apply(amount: Int) = new Dollar(amount) }
class Franc(protected val amount: Int) extends Money {
def *(multiplier: Int): Money = Franc(amount * multiplier)
}
object Franc { def apply(amount: Int) = new Franc(amount) }
// test code --------------------------------------------------------------------
import Money.{dollar, franc}
import org.scalacheck.Gen._
implicit val intGen: Gen[Int] = Gen.chooseNum(-1000000, 1000000)
implicit val consGen: Gen[Int => Money] = Gen.oneOf(Dollar(_:Int): Money, Franc(_:Int): Money)
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] = oneOf("USD", "CHF") 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 val unique3: Gen[(Money, Money, Money)] =
triple(moneyGen).suchThat { case (a, b, c) => a != b && b != c && c != a }
// 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("(m1 == m2) == (m1.getClass == m2.getClass)") =
forAll(double(consGen), intGen) { case ((c1, c2), n) =>
val m1 = c1(n)
val m2 = c2(n)
(m1 == m2) == (m1.getClass == m2.getClass)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment