Представляем вашему вниманию паттерн создания «мини-DSL» в Scala для работы с единицами измерения.
Одну реализацию этого шаблона можно увидеть в стандартной библиотеке Scala, а именно в scala.concurrent.duration._. Пример из документации Akka[1]:
В этом случае Int неявно преобразуется в объект с помощью метода «секунды», который затем возвращает тип, требуемый функцией.implicit val timeout = Timeout(5 seconds)
Далее мы рассмотрим поэтапное создание «мини-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
-
Как Разрабатывался Новый Дизайн Карт Maps.me
19 Oct, 24 -
Визуализация Ботнетов На Видео
19 Oct, 24 -
Как Дизайнеры Маска Победили Инженеров
19 Oct, 24 -
Примера: 3D-Мир
19 Oct, 24