https://gist.github.com/paulp/5560802
/* A better way to tag types? * * 1) object Time: here we are distinguishing between different uses of a Long, * yet there is no boxing whatsoever. * * main calls start: ()J * main calls timed: (Function0, J) * Function0 gives up the result: ()J * timed calls now: ()J * timed calls elapsed$extension: (JJ)J * main calls _2: ()J * * 2) object Bounds: Enumerate the acceptable uses. * Only Usage subclasses which appear in the lower bound can appear. */ import Time._ object Time { // Markers - these can have any structure or no structure. sealed trait Usage sealed trait StartTime extends Usage sealed trait CurrentTime extends Usage sealed trait CalendarTime extends Usage type Current = Timestamp[CurrentTime] type Start = Timestamp[StartTime] type Calendar = Timestamp[CalendarTime] // We could make the constructor private if we wanted to tightly // control instantiation. final class Timestamp[T <: Usage](val nanos: Long) extends AnyVal { // Originally I had this return a Nanos value class, but unfortunately // specialization doesn't work with value classes and it was boxed // in the return value of timed. def elapsed(implicit other: Current): Long = math.abs(other.nanos - nanos) override def toString = s"Timestamp($nanos)" } object Timestamp { implicit def start: Start = new Timestamp[StartTime](System.nanoTime) implicit def now: Current = new Timestamp[CurrentTime](System.nanoTime) def apply(date: java.util.Date): Calendar = new Timestamp[CalendarTime](date.getTime * 1000000L) } // If there were only one "Timestamp" class, the implicit parameter with the // start time stamp would be used as the implicit argument to elapsed rather // than the one in the Timestamp companion. def timed[@specialized T](body: => T)(implicit stamp: Start): (T, Long) = (body, stamp.elapsed) // public scala.Tuple2<java.lang.Object, java.lang.Object> timed$mDc$sp(scala.Function0<java.lang.Object>, long); // 0: new #81 // class scala/Tuple2$mcDJ$sp // 5: invokeinterface #85, 1 // InterfaceMethod scala/Function0.apply$mcD$sp:()D // 17: invokevirtual #31 // Method Time$Timestamp$.now:()J // 20: invokevirtual #35 // Method Time$Timestamp$.elapsed$extension:(JJ)J @annotation.tailrec def busywork(x: Double, reps: Long): Double = { if (reps <= 0) x else busywork((x + util.Random.nextInt) / 3, reps - 1) } def main(args: Array[String]): Unit = { val reps = args(0).toLong // 34: invokevirtual #66 // Method Time$Timestamp$.start:()J // 37: invokevirtual #70 // Method Time$.timed$mDc$sp:(Lscala/Function0;J)Lscala/Tuple2; // 40: invokevirtual #75 // Method scala/Tuple2._2$mcJ$sp:()J val ms = timed(busywork(0, reps))._2 / 1e6 println(f"$reps reps of busywork required $ms%.3f") } } object Bounds { def limited[T >: CurrentTime with StartTime <: Usage](implicit stamp: Timestamp[T]) = println(stamp) def f1 = limited(Timestamp.start) // compiles def f2 = limited(Timestamp.now) // compiles // def f3 = limited(Timestamp(new java.util.Date)) // fails // ./a.scala:65: error: type mismatch; // found : Time.Calendar // (which expands to) Time.Timestamp[Time.CalendarTime] // required: Time.Timestamp[Time.Usage] // Note: Time.CalendarTime <: Time.Usage, but class Timestamp is invariant in type T. // You may wish to define T as +T instead. (SLS 4.5) // def f3 = limited(Timestamp(new java.util.Date)) // ^ // one error found }