Created
July 18, 2017 07:26
-
-
Save tyrcho/037128103454b5e591a8f4e2eab605dd to your computer and use it in GitHub Desktop.
Scala Units of Measures, using implicits & generics
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
// inspired from http://bentrengrove.com/blog/2017/5/21/fun-with-types-extensions-and-generics-in-kotlin | |
// and http://javanut.net/2017/05/23/more-fun-with-generics-in-kotlin/ | |
// a complete library : http://www.squants.com/ | |
import Distance._ | |
import Time._ | |
trait UnitOfMeasure { | |
def name: String | |
def ratio: Double | |
def toBaseUnit(amount: Double): Double = amount * ratio | |
def fromBaseUnit(amount: Double): Double = amount / ratio | |
def /(u: UnitOfMeasure): QuotientUnit[this.type, u.type] = | |
QuotientUnit(this, u) | |
def *(u: UnitOfMeasure): ProductUnit[this.type, u.type] = | |
ProductUnit(this, u) | |
override def toString = name | |
} | |
case class Distance(name: String, ratio: Double) extends UnitOfMeasure | |
object Distance { | |
val Mile = Distance("Mile", 1609.344) | |
val Kilometer = Distance("Kilometer", 1000.0) | |
val Meter = Distance("Meter", 1.0) | |
val Centimeter = Distance("Centimeter", 0.01) | |
val Millimeter = Distance("Millimeter", 0.001) | |
} | |
case class Quantity[T <: UnitOfMeasure](amount: Double, unit: T) { | |
def to(other: T): Quantity[T] = { | |
val baseUnit = unit.toBaseUnit(amount) | |
Quantity(other.fromBaseUnit(baseUnit), other) | |
} | |
def +(other: Quantity[T]) = { | |
val converted = other.to(unit).amount | |
Quantity(amount + converted, unit) | |
} | |
// http://javanut.net/2017/05/23/more-fun-with-generics-in-kotlin/ | |
def *[S <: UnitOfMeasure](other: Quantity[S]): Quantity[ProductUnit[T, S]] = { | |
Quantity(amount * other.amount, ProductUnit(unit, other.unit)) | |
} | |
def /[S <: UnitOfMeasure](other: Quantity[S]): Quantity[QuotientUnit[T, S]] = { | |
Quantity(amount / other.amount, QuotientUnit(unit, other.unit)) | |
} | |
def /(other: Quantity[T]): Double = | |
unit.toBaseUnit(amount) / other.unit.toBaseUnit(other.amount) | |
} | |
implicit class DoubleQuantity(value: Double) { | |
def miles = Quantity(value, Distance.Mile) | |
def meters = Quantity(value, Distance.Meter) | |
def kilometers = Quantity(value, Distance.Kilometer) | |
// etc... | |
} | |
val tenMiles = 10.miles | |
val kilometers = tenMiles.to(Kilometer).amount | |
println(kilometers) | |
println(10.miles + 5.kilometers) | |
case class QuotientUnit[+A <: UnitOfMeasure, +B <: UnitOfMeasure](a: A, b: B) | |
extends UnitOfMeasure { | |
val name = s"$a/$b" | |
val ratio = a.ratio / b.ratio | |
} | |
case class ProductUnit[+A <: UnitOfMeasure, +B <: UnitOfMeasure](a: A, b: B) | |
extends UnitOfMeasure { | |
val name = s"$a*$b" | |
val ratio = a.ratio * b.ratio | |
} | |
case class Time(name: String, ratio: Double) extends UnitOfMeasure | |
object Time { | |
val Second = Time("Second", 1) | |
val Minute = Time("Minute", 60) | |
val Hour = Time("Hour", 3600) | |
} | |
implicit class DoubleTime(value: Double) { | |
def hours = Quantity(value, Time.Hour) | |
def minutes = Quantity(value, Time.Minute) | |
def seconds = Quantity(value, Time.Second) | |
} | |
val distance = 21.kilometers | |
val time = 1.5.hours | |
val speed = distance / time | |
val ms = Meter / Second | |
val speedMS = speed.to(ms) | |
println(s"Speed is: $speed") // Speed is: 14 km/h | |
println(s"Speed is: $speedMS") | |
val trainSpeed = 60.miles / 1.hours | |
val jamesBondSpeed = 20.meters / 1.minutes | |
val totalSpeed = trainSpeed + jamesBondSpeed | |
val metricSpeed = totalSpeed.to(Kilometer / Hour) | |
println(metricSpeed) | |
println(s"Speed ratio: ${jamesBondSpeed / totalSpeed}") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment