Паттерн Создания Dsl В Scala Для Работы С Единицами Измерения

Представляем вашему вниманию паттерн создания «мини-DSL» в Scala для работы с единицами измерения.

Одну реализацию этого шаблона можно увидеть в стандартной библиотеке Scala, а именно в scala.concurrent.duration._. Пример из документации Akka[1]:

  
  
  
  
  
  
   

implicit val timeout = Timeout(5 seconds)

В этом случае Int неявно преобразуется в объект с помощью метода «секунды», который затем возвращает тип, требуемый функцией.

Далее мы рассмотрим поэтапное создание «мини-DSL» для работы с частотой.

В конечном итоге планируется возможность задавать частоту естественным образом, например, 5 кГц.

Прежде чем приступить к реализации шаблона, вам необходимо создать класс для хранения единицы измерения, будь то время, уровень сигнала или частота.

Например:

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" }

После создания класса для хранения единицы измерения необходимо обозначить все возможные ее представления и правила их преобразования друг в друга.

Для частоты это Гц, кГц, МГц, ГГц.

Пример:

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) } …… }

Вышеупомянутое является реализацией только для Гц.

Остальные делаются таким же образом.

Их можно просмотреть на Github по ссылке в конце статьи.

В случае стандартной библиотеки Scala правила преобразования указываются в перечислении (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)) } }

Теперь, когда у нас есть класс для хранения единицы измерения, а также правила ее преобразования, нам нужно создать метод неявного преобразования и добавить его в область видимости.

Удобнее было бы создать «пакет-объект»:

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) } }

Каждый неявный класс добавляет тип, из которого можно получить частоту.

Теперь мы можем использовать частоту естественным образом.

Пример:

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 scala> 10.Hz + 5.Hz res1: org.nd.frequency.Frequency = 15 Hz

Полный исходный код с примерами можно найти в репозитории .

ОБНОВЛЯТЬ 1. Использование постфиксной нотации для вызова методов небезопасно и не рекомендуется .

Добавлен вариант с обычными обозначениями.

Спасибо Гуголплекс .

2. В статью добавлен миксин FrequencyConversions. Спасибо велет5 .



Список использованных источников

1. Фьючерсы.

Документация Акка.

Раздел «Использование с актерами».

Теги: #scala #dsl #программирование #scala

Вместе с данным постом часто просматривают: