Skip to content

Instantly share code, notes, and snippets.

@tyrcho
Created July 18, 2017 07:26
Show Gist options
  • Save tyrcho/037128103454b5e591a8f4e2eab605dd to your computer and use it in GitHub Desktop.
Save tyrcho/037128103454b5e591a8f4e2eab605dd to your computer and use it in GitHub Desktop.
Scala Units of Measures, using implicits & generics
// 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