Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Библиотека по работе с единица системы СИ KotUniL, разработанная изначально на Kotlin, недавно сделана мультиплатформенной. В частности, она доступна теперь и на JavaScript, о чём написано здесь.
А зачем нужна эта библиотека?
Программируя традиционным образом, очень просто упустить из виду, в каких единицах измеряются числа, с которыми мы оперируем. Например, можно сложить метры с литрами, и никакой компилятор нам при этом не поможет.
Эта проблема реальная и уже доказано приводила к авариям, обошедшимся во многие миллионы долларов. (Одна из историй здесь).
Избежать этого помогает использование специальных библиотек, которые корректно работают с физическими единицами системы СИ типа метров или ваттов и иными единицами типа валют или штук.
Одна из таких библиотек - KotUniL (si-units).
Использование технологии Kotlin Multiplatform позволяет из кода на Kotlin получить код для целого ряда платформ, в том числе для JVM.
А это означает, что тем самым реализованную в библиотеке функциональность можно использовать из Java-программ. (Строго говоря, этого можно было достичь и не используя Kotlin Multiplatform, но в данном случае мы получаем JVM-библиотеки автоматически вместе с другими вариантами, что экономит наши усилия).
Как её использовать?
А как использовать библиотеку из Java?
Подключить библиотеку в ваш проект можно как dependency:
repositories {
mavenCentral()
}
dependencies {
implementation("eu.sirotin.kotunil:kotunil-jvm:<version>")
}
На момент написания статьи последняя версия была 4.1.1
Какие различия между вариантами на Kotlin и Java существуют?
А дальше всё тоже самое, как в Kotlin? К сожалению, есть два важных отличия.
Во первых, Java не поддерживает operator overloading и поэтому код смотрится менее элегантно, чем на Котлин.
Рассмотри, например, такую задачку: Маша протирала снаружи стекло аквариума, задела стоявшую рядом вазу, в результате чего стекло аквариума разбилось и вода вытекла на пол. В аквариуме до этой неприятности было 32 литра воды. Комната Маши имеет длину 4 метра и ширину 4,3 метра. На какой высоте в мм. находится сейчас вода в комнате, при условии, что она осталась там и не вытекла?
На Котлине решение выглядит таким образом:
val s = 4.m * 4.3.m
val h = 32.l/s
А на Java нам приходится вместо знаков арифметических операций над размерными единицами использовать функции:
Expression s = m.times(4).times(m.times(4.3));
Expression v = L.times(32);
Expression h = v.div(s)
Однако все остальные прелести KotUniL сохранены. Амперы с секундами складывать нельзя, а перемножать и делить можно.
Во-вторых, трансформируя код Kotlin в код на Java, транспилер создаёт для классов библиотеки из одного Kotlin-класса два Java-класса. Поэтому, например, работая с метрами мы должны в Java-импорты добавлять строку:
import static eu.sirotin.kotunil.base.MetreKt.*;
Дальнейшую информацию о KotUniL вы надаете в репозитории на GitHub ( см. подробный пример работы с единицами системы СИ в Java в модуле app/jvm/java-console).
Про теоретические основы и особенности использования KotUniL вы можете прочитать и в этой серии стаей:
Магия размерностей и магия Котлина. Часть первая: Введение в KotUniL
Магия размерностей и магия Котлина. Часть вторая: Продвинутые возможности KotUniL
Магия размерностей и магия Котлина. Часть третья: Смешение магий
А есть ли альтернативы?
Я разработал KotUniL, поскольку ни одна из существовавших на тот момент библиотек меня не удовлетворяла. В Java сообществе существует JSR 385. Очевидным образом у разработчиков этой спецификации были схожие с моими мотивации. Но там разработчики пошли по пути выделения размерностей. У них получились Quantity<Volume>
, Unit<Length>
и так далее. Разумеется, всех возможных комбинаций таким образом не покрыть, но множеству приложений этого делать и не придётся.
Вот статья на Baeldung про общий вид работы с библиотекой.
Дизайн библиотеки мне не понравился. Чего только стоит подобный пример конвертирования метров в километры, взятый мной из рекомендованной выше статьи!:
double distanceInMeters = 50.0;
UnitConverter metreToKilometre = METRE.getConverterTo(MetricPrefix.KILO(METRE));
double distanceInKilometers = metreToKilometre.convert(distanceInMeters );
Используя KotUniL вы это запишите так:
val d = 50.m
val x = d.km
Я понимаю, преимущество JSR 385 состоит в обнаружении ошибок типизации на этапе компиляции, а не в наглядности краткости кода. В этой статье я показываю, что любые формулы KotUniL проверяются на ошибки типизации ровно одним юнит-тестом при наглядности кода, схожей с наглядностью технических и научных статей.
Почему стандарт СИ не реализован в Kotlin?
Итак, в мире Java существует по крайней мере спецификация JSR 385, касающаяся реализации обработки единиц системы СИ, а как обстоят с эти дела в Kotlin?
К сожалению, в числе существующих и разрабатываемых стандартных библиотек Kotlin такой библиотеки пока нет.
Я предложил разработчикам языка включить эту функциональность (не обязательно KotUniL) в состав библиотек, поставляемых вместе с языком: (KT-55556). Но пока не убедил их. Но возможно, если вы, уважаемые читатели, активно проплюсуете это предложение ( а заодно и KotUniL на GitHub :-), то ситуация исправится.