Created
March 3, 2017 23:02
-
-
Save btlines/bcee65f5614fb73750e04c405e06497c to your computer and use it in GitHub Desktop.
FinanceDSL with automatic class type derivation using Shapeless
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import shapeless.{ ::, Generic, HList, HNil, Lazy } | |
object ShapelessDSL extends App { | |
trait Amount[A] { | |
def zero: A | |
def plus(a: A, b: A): A | |
def times(a: A, b: BigDecimal): A | |
} | |
object Amount { | |
implicit val amountForBigDecimalHList: Amount[BigDecimal :: HNil] = | |
new Amount[BigDecimal :: HNil] { | |
override def zero: BigDecimal :: HNil = BigDecimal(0) :: HNil | |
override def plus(a: BigDecimal :: HNil, b: BigDecimal :: HNil): BigDecimal :: HNil = | |
(a.head + b.head) :: HNil | |
override def times(a: BigDecimal :: HNil, b: BigDecimal): BigDecimal :: HNil = | |
(a.head * b) :: HNil | |
} | |
implicit def genericAmount[A, R](implicit gen: Generic.Aux[A, R], amount: Amount[R]): Amount[A] = | |
new Amount[A] { | |
override def zero: A = gen.from(amount.zero) | |
override def plus(a: A, b: A): A = gen.from(amount.plus(gen.to(a), gen.to(b))) | |
override def times(a: A, b: BigDecimal): A = gen.from(amount.times(gen.to(a), b)) | |
} | |
implicit def amountForHList[H, T <: HList]( | |
implicit amount: Lazy[Amount[H]], defaultTail: DefaultInstance[T] | |
): Amount[H :: T] = new Amount[H :: T] { | |
override def zero: H :: T = amount.value.zero :: defaultTail.instance | |
override def plus(a: H :: T, b: H :: T): H :: T = amount.value.plus(a.head, b.head) :: defaultTail.instance | |
override def times(a: H :: T, b: BigDecimal): H :: T = amount.value.times(a.head, b) :: defaultTail.instance | |
} | |
implicit class AmountOps[A](a: A)(implicit amount: Amount[A]) { | |
def +(b: A): A = amount.plus(a, b) | |
def -(b: A): A = amount.plus(a, amount.times(b, -1)) | |
def *[B](b: BigDecimal): A = amount.times(a, b) | |
def /[B](b: BigDecimal): A = amount.times(a, BigDecimal(1) / b) | |
} | |
} | |
trait DefaultInstance[T] { | |
def instance: T | |
} | |
object DefaultInstance { | |
def apply[A](a: A): DefaultInstance[A] = new DefaultInstance[A] { | |
override def instance = a | |
} | |
implicit def defaultInstanceForHList[H, T <: HList]( | |
implicit defaultHead: DefaultInstance[H], defaultTail: DefaultInstance[T] | |
): DefaultInstance[H :: T] = new DefaultInstance[H :: T] { | |
override def instance: H :: T = | |
defaultHead.instance :: defaultTail.instance | |
} | |
// Now we need a stop case: a DefaultInstance for HNil | |
implicit val defaultInstanceForHNil: DefaultInstance[HNil] = | |
new DefaultInstance[HNil] { | |
override def instance: HNil = HNil | |
} | |
} | |
object Period { | |
sealed trait Month | |
sealed trait Year | |
final val Month = new Month() {} | |
final val Year = new Year() {} | |
implicit val defaultInstanceForMonth: DefaultInstance[Month] = DefaultInstance(Month) | |
implicit val defaultInstanceForYear: DefaultInstance[Year] = DefaultInstance(Year) | |
} | |
object Surface { | |
sealed trait SquareMeter | |
final val SquareMeter = new SquareMeter {} | |
implicit val defaultInstanceForSquareMeter: DefaultInstance[SquareMeter] = DefaultInstance(SquareMeter) | |
} | |
case class Per[A, B](value: A, unit: B) | |
implicit class SmartConstructorForPer[A](a: A) { | |
def per[B](b: B) = Per(a, b) | |
} | |
import DefaultInstance._ | |
import Period._ | |
import Surface._ | |
import Amount._ | |
case class GBP(value: BigDecimal) | |
case class EUR(value: BigDecimal) | |
case class USD(value: BigDecimal) | |
val twentyPounds = GBP(15) + GBP(5) | |
val twentyPoundsPerMonth = twentyPounds per Month | |
val fortyPoundsPerMonth = twentyPoundsPerMonth + twentyPoundsPerMonth | |
val pricePerSquareMeter = EUR(1000) per SquareMeter | |
val expensiveSquareMeter = pricePerSquareMeter + pricePerSquareMeter | |
val rentingPotential = USD(1800) per SquareMeter per Year | |
val estimatedCost = USD(1500) per SquareMeter per Year | |
val estimatedRevenue = rentingPotential - estimatedCost | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment