Scala DSL pattern for unit handling
A pattern for creating a “mini-DSL” on Scala for operating units will be presented to your attention. One of the implementations of this pattern can be seen in the Scala standard library, namely in scala.concurrent.duration._. An example from the documentation for Akka [1]:
In this case, Int is implicitly converted to an object with the “seconds” method, which then returns the type required by the function.
Next, we will consider the step-by-step creation of a “mini-DSL" for operating with a frequency. Ultimately, it is planned to be able to set the frequency in a natural way, for example, 5 kHz.
Before you start implementing the pattern, you need to create a class to store the unit of measure, be it time, signal level, or frequency. For instance:
After creating a class for storing a unit of measure, it is necessary to identify all its possible representations and the rules for converting them into each other. For frequency, these are Hz, kHz, MHz, GHz. Example:
The above implementation is for Hz only. The rest are done in the same way. They can be viewed on the github at the link at the end of the article. In the case of the standard Scala library, conversion rules are specified in enum (java.util.concurrent.TimeUnit).
Add a companion object with the apply method to the Frequency class to create the frequency:
Now that we have a class for storing a unit of measure, as well as rules for its conversion, we need to create a method for implicit conversion and add it to the scope. It will be more convenient to create a “package-object”:
Each implicit class adds a type from which a frequency can be derived. Now we can use the frequency in a natural way. Example:
You can add addition and multiplication operations in the future. In this case, the syntax will not look so natural, since you have to put brackets around each expression or a dot after the number:
The full source code with examples can be viewed in the repository .
UPDATE
1. Using postfix notation to invoke methods is unsafe and not recommended . Added option with the usual notation. Thanks Googolplex .
2. Added an admixture of FrequencyConversions to the article. Thanks velet5 .
1. Futures. Akka Documentation. Section "Use With Actors".
implicit val timeout = Timeout(5 seconds)
In this case, Int is implicitly converted to an object with the “seconds” method, which then returns the type required by the function.
Next, we will consider the step-by-step creation of a “mini-DSL" for operating with a frequency. Ultimately, it is planned to be able to set the frequency in a natural way, for example, 5 kHz.
Before you start implementing the pattern, you need to create a class to store the unit of measure, be it time, signal level, or frequency. For instance:
class Frequency(val hz: BigInt) {
require(hz >= 0, "Frequency must be greater or equal to zero!")
def +(other: Frequency) = new Frequency(hz + other.hz)
override def toString: String = hz.toString + " Hz"
}
After creating a class for storing a unit of measure, it is necessary to identify all its possible representations and the rules for converting them into each other. For frequency, these are Hz, kHz, MHz, GHz. Example:
sealed trait FrequencyUnitScala {
def toHz(n: BigInt): BigInt
def toKHz(n: BigInt): BigInt
def toMHz(n: BigInt): BigInt
def toGHz(n: BigInt): BigInt
def convert(n: BigInt, unit: FrequencyUnitScala): BigInt
}
object Hz extends FrequencyUnitScala {
override def toHz(n: BigInt): BigInt = n
override def toGHz(n: BigInt): BigInt = toMHz(n) / 1000
override def toKHz(n: BigInt): BigInt = n / 1000
override def toMHz(n: BigInt): BigInt = toKHz(n) / 1000
override def convert(n: BigInt, unit: FrequencyUnitScala): BigInt = unit.toHz(n)
}
……
}
The above implementation is for Hz only. The rest are done in the same way. They can be viewed on the github at the link at the end of the article. In the case of the standard Scala library, conversion rules are specified in enum (java.util.concurrent.TimeUnit).
Add a companion object with the apply method to the Frequency class to create the frequency:
object Frequency {
def apply(value: BigInt, unit: FrequencyUnitScala): Frequency = unit match {
case frequency.Hz => new Frequency(value)
case u => new Frequency(u.toHz(value))
}
}
Now that we have a class for storing a unit of measure, as well as rules for its conversion, we need to create a method for implicit conversion and add it to the scope. It will be more convenient to create a “package-object”:
trait FrequencyConversions {
protected def frequencyIn(unit: FrequencyUnitScala): Frequency
def Hz = frequencyIn(frequency.Hz)
def kHz = frequencyIn(frequency.kHz)
def MHz = frequencyIn(frequency.MHz)
def GHz = frequencyIn(frequency.GHz)
}
package object frequency {
implicit final class FrequencyInt(private val n: Int) extends FrequencyConversions {
override protected def frequencyIn(unit: FrequencyUnitScala): Frequency = Frequency(n, unit)
}
}
Each implicit class adds a type from which a frequency can be derived. Now we can use the frequency in a natural way. Example:
scala> import org.nd.frequency._
import org.nd.frequency._
scala> println(1 Hz)
1 Hz
scala> println(1 kHz)
1000 Hz
scala> println(1 MHz)
1000000 Hz
scala> println(1 GHz)
1000000000 Hz
You can add addition and multiplication operations in the future. In this case, the syntax will not look so natural, since you have to put brackets around each expression or a dot after the number:
scala> val sum = (3000 kHz) + (2 MHz)
sum: org.nd.frequency.Frequency = 5000000 Hz
scala> println("3000 kHz + 2 MHz equals " + sum.toKHz)
3000 kHz + 2 MHz equals 5000 kHz
scala> 10.Hz + 5.Hz
res1: org.nd.frequency.Frequency = 15 Hz
The full source code with examples can be viewed in the repository .
UPDATE
1. Using postfix notation to invoke methods is unsafe and not recommended . Added option with the usual notation. Thanks Googolplex .
2. Added an admixture of FrequencyConversions to the article. Thanks velet5 .
List of sources used
1. Futures. Akka Documentation. Section "Use With Actors".