[Из песочницы] Паттерн для cоздания DSL на Scala для оперирования единицами измерения

Вашему вниманию будет представлен паттерн для создания «мини-DSL» на Scala для оперирования единицами измерения. Одну из реализаций этого паттерна можно увидеть в стандартной библиотеке Scala, а именно — в scala.concurrent.duration._. Пример из документации по Akka[1]: implicit val timeout = Timeout (5 seconds) В данном случае Int неявно конвертируется в объект с методом «seconds», который затем возвращает требуемый функции тип.Далее будет рассмотрено пошаговое создание «мини-DSL» для оперирования частотой. В конечном итоге планируется получить возможность задавать частоту естественным образом, например, 5 kHz.Перед тем, как приступить к реализации паттерна, необходимо создать класс для хранения единицы измерения, будь то время, уровень сигнала или частота. Например:

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» } После создания класса для хранения единицы измерения необходимо обозначить все возможные её представления и правила конвертации для них друг в друга. Для частоты — это Hz, kHz, MHz, GHz. Пример: 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) } …… } Выше представлена реализация только для Hz. Остальные делаются аналогично. Их можно посмотреть на гитхабе по ссылке в конце статьи. В случае со стандартной библиотекой Scala правила конвертации заданы в enum (java.util.concurrent.TimeUnit).Добавим классу Frequency объект-компаньон с методом apply для создания частоты:

object Frequency { def apply (value: BigInt, unit: FrequencyUnitScala): Frequency = unit match { case frequency.Hz => new Frequency (value) case u => new Frequency (u.toHz (value)) } } Теперь, когда у нас есть класс для хранения единицы измерения, а также правила для её конвертации, нужно создать способ неявной конвертации и добавить его в область видимости. Удобнее будет создать «package-object»: package object frequency { implicit final class FrequencyInt (private val n: Int) extends FrequencyConversions { override protected def frequencyIn (unit: FrequencyUnitScala): Frequency = Frequency (n, unit) } } Каждый неявный класс добавляет тип, из которого может быть получена частота. Теперь мы можем использовать частоту естественным образом. Пример: 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 В дальнейшем можно добавлять операции сложения и умножения. К сожалению, в этом случае синтаксис будет выглядеть не так естественно, так как придётся ставить скобки вокруг каждого выражения, чтобы компилятор нас понял: 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 Полный исходный код с примерами можно посмотреть по адресу: github.com/Venje/frequencyDsl/Список использованных источников 1. Futures. Akka Documentation. Секция «Use With Actors».

© Habrahabr.ru