📜 ⬆️ ⬇️

Pattern for creating DSL on Scala for operating with units

A pattern for creating a “mini DSL” on Scala for operating with units of measurement will be presented to your attention. One of the implementations of this pattern can be seen in the standard Scala library, namely in scala.concurrent.duration._. Example from Akka documentation [1]:

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 of function required.

Next will be considered the step-by-step creation of a “mini DSL” for operating the frequency. In the end, it is planned to be able to set the frequency in a natural way, for example, 5 kHz.

Before proceeding to the implementation of the pattern, you need to create a class to store the unit of measurement, be it time, signal level or frequency. For example:
')
 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 measurement, it is necessary to designate all its possible representations and the rules for converting them into each other. For frequency, it is 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 is an implementation only for Hz. The rest are done similarly. They can be viewed on the githab by the link at the end of the article. In the case of the standard Scala library, the conversion rules are specified in enum (java.util.concurrent.TimeUnit).

Add a companion object to the Frequency class with the apply method to create a 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 the unit of measurement, as well as rules for converting it, we need to create a way 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 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 

In the future, you can add operations of addition and multiplication. In this case, the syntax will not look so natural, as 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 

Full source code with examples can be viewed in the repository .

UPDATE
1. Using postfix notation to call methods is unsafe and not recommended . Added a variant with the usual notation. Thank you Googolplex .
2. Added impurity FrequencyConversions to the article. Thank you velet5 .

List of used sources


1. Futures. Akka Documentation. Section "Use With Actors".

Source: https://habr.com/ru/post/250787/


All Articles