Skip to content

Instantly share code, notes, and snippets.

@telekosmos
Created January 8, 2022 16:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save telekosmos/a8f1e7c47e841b6afeb1099406d97d43 to your computer and use it in GitHub Desktop.
Save telekosmos/a8f1e7c47e841b6afeb1099406d97d43 to your computer and use it in GitHub Desktop.

(Mostly taken from here)

If I want to make a benchmark module, I'll usually resort to clock facilities provided by the standard libraries or, at worse, I'll declare a clock type and request to be initialized with a class implementing it before the module's functionality is ready to be used.

When module support in the language is available, however, I'll not only declare the benchmark interfaces I provide, but I'll also declare that I need a "clock module" -- a module exporting certain interfaces that I need.

A client of my module would not be required to do anything to use my interfaces -- it could just go ahead and use it. Or it could not even declare that my benchmark module would be used and, instead, declare that it has a requirement for that module.

What will satisfy the requirements is only decided at the "top" level, at the application level (modules are components of applications). At that point it would declare that it would use that client (my benchmark) and a module implementing my clock requirements.

How does module support work in Scala? Well, the interface of benchamark module might look like this:

trait Benchmark extends Clock { // What I need is a Clock
  // What I provide
  type ABench <: Bench
  trait Bench {
    def measure(task: => Unit): Long
  }
  def aBench: ABench
}

And a Clock would be a module definition such as:

trait Clock { // This on doesn't need anything
  // What it provides
  type AClock <: Clock
  trait Clock {
    def now(): Long
  }
  def aClock: AClock
}

The benchmark module might look like this

trait BenchmarkModule extends Benchmark {
  class ABench extends Bench {
    def measure(task: => Unit): Long = {
      val measurements = for(_ <- 1 to 10) yield {
        val start = aClock.now()
        task
        val end = aClock.now()
        end - start
      }
      measurements / 10
    }
  }
  object aBench extends ABench
}

And the ClockModule

trait ClockModule extends Clock {
  class AClock extends Clock {
    def now(): Long = DateTime.now.millis // figured
  } 
  object aClock extends AClock
}

An application could be declared as being a composition of modules:

trait Application extends Benchmark with Clock // with ...

though, of course, dependencies need not be declared, as they are already provided for.

You can then combine modules that provide the requirements to build the application:

object Application extends BenchmarkModule with ClockModule

And that would link the requirements of BenchmarkModule with the implementation provided by ClockModule.

All the stuff above still require some shared knowledge, because "Clock" is likely, an API that I provide (as seen). One can write a proxy, of course, but it's not plug and play. Scala can go a bit further, if I declared my Benchmark module like this:

trait BenchmarkBis {
  type Clock = {
    def now(): Long
  }
  def aClock: Clock

  // What I provide
  type ABench <: Bench
  trait Bench {
    def measure(task: => Unit): Long
  }
  def aBench: ABench
}

Now any class that offers a now(): Long method can be used to satisfy the requirement, without any bridge. Names has to match though:

trait XClockModule {
  class XClock {
    def now(): Long = Random.nextLong + 1
  }
  object aClock extends XClock // This object **has to be named** aClock
}

And this can give compiler warning because of a reflective call.

So, that's the module, and the module support. Finally first class module. First class support for X means X can be manipulated as values. For example, Scala has first class support for functions, which means I can pass a function to a method, store it in a variable, in a map, etc.

The first class support for modules, is basically instantiation, though one can use an "object" to create a singleton of that module, and then pass that around. Then, we can do:

object myBenchmark extends BenchmarkModule with ClockModule

and pass myBenchmark as a parameter to methods that need such a module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment