Search
Write a publication
Pull to refresh

Comments 34

Формулы и правда выглядят прикольно. А как с этим всем работает комплишен? Будет удобно нащелкать символ евро из предложенных вариантов на 5. или проще учиться набирать юникод с клавиатуры?

Потерпите. Про использование валют в расчётах я напишу во второй части статьи.

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

Спасибо на добром слове.

Эти абстракции выстроила то ли сама природа, то ли пределущие поколения великих физиков и математиков, то ли они вместе. Именно это я и называю магией размерностей. Подробнее об этом я собираюсь рассказать в третей статье этой серии.

С точки зрения системы типов (как бы Вы не утверждали, что то, что получилось у Вас, лучше), было бы корректнее добавлять для каждой из таких единиц измерения свой тип. И тогда все такие ошибки ловились бы не при "пробегающем" юнит-тесте, а еще при компиляции. Чем раньше мы находим ошибку, тем лучше

Спору нет, находить все ошибки при манипулировании с физическими и иными единицами уже на этапе компиляции было бы замечательно. Но увы, пока моя слабая фантазия не видит для этого другого способа кроме как создавать свой тип на каждую комбинацию размерностей - скорость, ускорение, плотность, и т.д. А их - безбрежное количество.

К тому же, это противоречит «логике физики», насколько я ее себе представляю.

Я размышляю сейчас над возможностью написания плагина на KSP для предпроцессинга кода с использованием KotUniL. Но пока, честно говоря, решения не нашёл. Да и по затратам это будет не так уж мало. А я тяну проект пока один в свободное от остальных забот время.

Кодогенерация может помочь. Описываются основные величины а дальше декартовым произведением перемножаются на возможные операции.

Не думаю, что это реально. Возьмём например скорость. Скорость движения - размерность m/s, скорость окраски забора - m2/s, скорость таяния льда - m3/s. Простое ускорение: m/s2, ускорение при окраске забора m/s2... и т.д. Это только простейшие комбинации двух размерностей. А много величин - комбинаций трёх, четырёх, пяти размерностей и т.д. Степени могут быть положительные и отрицательные. И главное - как упомнить названия получившихся классов? Уж тогда проще новый язык сделать. А мы хотим оставаться в Котлине.

Новый язык не одязательно делать, это уже реализовано в F#.

Вы имеете ввиду Units of mesure?

Насколько я могу судить, это менее удобно, чем KotUniL и беднее по функциональности. Или в F# существует ещё что-то на эту тему?

В JSR 385 для Java именно что пошли по пути выделения размерностей. У них получились Quantity<Volume>, Unit<Length>, и так далее. Разумеется, всех возможных комбинаций таким образом не покрыть, но множеству приложений этого делать и не придётся. При этом, стандартные размерности покрываются стандартным же образом, а при достаточно гибком дизайне библиотеки новые нужные для конкретно вашего приложения размерности добавить должно быть достаточно просто, и при этом не теряется типобезопасность.


Описаний хороших найти что-то не получается, но вот статья на Baeldung про общий вид работы с библиотекой.


UPD: Ну как я раньше не подумал пойти на главный сайт и потыкаться там?
Ссылки:


Я посмотрел примеры в рекомендованной Вами статье. Это мазохизм создателей и садизм по отношению к потенциальным пользователям. Чего стоят подобный пример конвертирования метров в километры, взятый мной из рекомендованной Вами статьи:

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

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

В третьей статье этой серии я постараюсь показать, что любые формулы KotUniL проверяются ровным одним юнит-тестом.

Ничто на самом деле не мешает разработчикам JSR 385 сделать дополнительный метод


public Quantity<Q> convertTo(Unit<Q> quantity);

И тогда то, что вам не понравилось, будет выглядеть как-то так:


Quantity<Length> distance = getQuantity(2500d, METRE);
Quantity<Length> inKilometers = distance.convertTo(KILO(METRE));

А это, согласитесь, уже примерно то же, что и у вас, с поправкой на отсутствие в Java таких же возможностей по добавлению синтаксического сахара. И тайпчекается на ура.


Я же не предлагаю вам полностью повторить тот API. Там на мой взгляд многовато всяческих "мин" подложено — да хотя бы тот факт, что у них Unit параметризуется через Quantity, хотя по всем моим инстинктам они оба должны быть параметризованы через Dimension.


UPD: Собственно, там уже есть такой метод, как я предложил: Quantity::to

Кроме длины в СИ есть ещё шесть других базовых размерностей. И 22 их распространнённых комбинации и сотни менее распространённых. И они могут сами далее комбинироваться.

Для каждой из этих комбинаций Вы предлагаете отдельный тип создавать?

А что такого?


Вот список предопределённых размерностей из JSR, например:
  • Acceleration
  • Amount of Substance
  • Angle
  • Area
  • Catalytic Activity
  • Dimensionless
  • Electric Capacitance
  • Electric Charge
  • Electric Conductance
  • Electric Current
  • Electric Inductance
  • Electric Potential
  • Electric Resistance
  • Energy
  • Force
  • Frequency
  • Illuminance
  • Length
  • Luminous Flux
  • Luminous Intensity
  • Magnetic Flux
  • Magnetic Flux Density
  • Mass
  • Power
  • Pressure
  • Radiation Dose Absorbed
  • Radiation Dose Effective
  • Radioactivity
  • Solid Angle
  • Speed
  • Temperature
  • Time
  • Volume

Безотносительно порой довольно странного содержания этого списка, покрыто достаточно много вариантов. И это в Java. В Kotlin можно через extension'ы доопределить, примерно так


  fun Quantity<Length>.times(Quantity<Length>): Quantity<Area>

  fun Quantity<Length>.div(Quantity<Time>): Quantity<Speed>

Приведённый Вами список не что иное, как соединение так называемых базовых и производных величин СИ. А ещё есть неофициальные, но признаваемые системой единицы, вроде гектара и литра. А ещё существуют национальные варианты типа кг. вместо kg. А ещё хорошо бы валюты так-же просто использовать. И пользователь свои единицы должен мочь определять. И - последний гвоздь в гроб этой идеи: очень часто нужны более сложные единицы, получаемые умножением или делением вышеперечисленных.

Вывод: это тупиковый путь.

А ещё есть неофициальные, но признаваемые системой единицы, вроде гектара и литра

Гектар это Area, литр это Volume. Они не строго дополняют список размерностей, они ему ортогональны, вообще говоря. Посмотрите в примерах и спеке у JSR, дюймы и метры там оба относятся к Unit<Length>, точно так же, как кубометры и литры относятся к Unit<Volume>.


А ещё существуют национальные варианты типа кг. вместо kg.

Зачем такое явно включать в программную модель? У java.time (да и в принципе в библиотеках, где присутствует локализация) все такие вещи находятся на внешнем крае библиотеки, где-нибудь в Parser/Formatter. У вас же в библиотеке нельзя написать val weight = 5.千克? Вот и в любой другой это не пригодится. Но при большом желании, конечно, можно все эти имена методов сгенерировать.


А ещё хорошо бы валюты так-же просто использовать.

Это совершенно не то же самое, что к метру прибавить ярд, не получится у нас прибавить евро к доллару без доступа в Интернет. Monetary — это в целом нормальный тип данных в нескольких виденных мной библиотеках, но там совершенно отдельная банка червей с подключением внешних сервисов конверсии (да хотя б через SPI) и правилами, разрешающими или запрещающими сложение количеств с разными валютами в конкретных местах приложений.


И пользователь свои единицы должен мочь определять.

А в чём проблема с этим в JSR-385?


И — последний гвоздь в гроб этой идеи: очень часто нужны более сложные единицы, получаемые умножением или делением вышеперечисленных.

Я ведь выше уже написал, как умножение и деление ввести в такую систему, и что в итоге даётся возможность полностью управлять типом результата (и вот там, кстати, и нужны юнит-тесты, а во всём остальном коде они будут ни к чему). В Kotlin это вообще куда проще сделать, чем в Java — у вас компаньоны есть, и операторы-по-конвенции, а в Java придётся это всё в статические методы запихать.


Вывод: это тупиковый путь.

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


Но библиотека с альтернативным подходом сейчас есть, ей пользуется явно ненулевое количество приложений. Несмотря на моё несогласие с выбранным списком абстракций конкретно в JSR-385, её редизайн, на мой взгляд, мог бы стать очень мощным инструментом в перспективе, а в Kotlin с его количеством сахара и вовсе мёдом намазано попытаться.

Вы говорите сразу о как минимум двух темах: JSR 385 и возможных изменениях/улучшениях в KotUniL.

Я бы хотел эти темы разделить. Поэтому далее отвечаю на Ваш длинный пост по этим темам раздельно.

О JSR 365. Как я уже говорил, я не думаю, что из этого получится что-то интересное с точки зрения практики. Я попытался это вверху обосновать.

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

О KotUniL.

Я предложил эту или подобную библиотеку наконец-то вставить в стандартную поставку Котлина: https://youtrack.jetbrains.com/issue/KT-55556

Так что далее можно дискутировать уже конкретно в рамках этой заявки.

Например, если у Вас есть какие-то соображения по синтаксису - Вы можете переписать примеры из статей этой серии в Вашем синтаксисе. Мне очень интересно было бы понять Ваши предложения.

А пока несколько замечаний к Вашему комментарию.

  1. Я считаю, что в Котлине необходимы библиотеки для работы с национальными единицами. Т.е. должно быть возможно написать на большинстве языков что-то вроде: val скорость = 60.км/час. Чтобы не перегружать поставку, это должно быть решено с помошью национальных вариантов библиотеки для популярных языков/культур.

  2. Цели конвертировать валюты друг в друга не ставится. Это сложная функция, зависящая от времени, места, юрисдикций партнёров и т.д. Доллар и рубль это такие же единицы как секунда и киловатт или просто штука (посмотрите пример с Thing) в README.md в KotUniL. И манипулирование со всеми единицами происходит по одним и тем же правилам.

Я предложил эту или подобную библиотеку наконец-то вставить в стандартную поставку Котлина

Так как ваш вариант не предлагает проверки несоответствия типов значений на уровне компиляции, я считаю, что встраивание её в стандартную библиотеку серьёзно навредит возможному развитию этой библиотеки в будущем, потому что развитие этого API с добавлением проверок на этапе компиляции будет невозможно без нарушения обратной совместимости. Примерно так в своё время навредили стандартной библиотеке Java типы java.util.Date и java.util.Calendar, каждый со своими хвостами неправильных предположений о датах и времени, и кучам legacy-кода, который уже не переделать.
В виде обычной библиотеки я против вашего варианта API ничего не имею, тогда каждый найдёт что-то для себя, и будущее развитие никак не пострадает.


Я считаю, что в Котлине необходимы библиотеки для работы с национальными единицами. Т.е. должно быть возможно написать на большинстве языков что-то вроде: val скорость = 60.км/час.

Это не имеет смысла пока в Kotlin нет, например,
kt.Последовательность<out T>.
Стандартная библиотека на то и стандартная, что представляет собой общий словарь, понятный любому программисту. Её перевод на другие языка дробит сообщество и создаёт анклавы программистов, неспособных читать код других программистов. Это явно противоречит интересам тех, кто разрабатывает и поддерживает стандартную библиотеку. Уже хотя бы потому, что вместо одной стандартной библиотеки появляется огромное множество региональных стандартных библиотек, несовместимых между собой, это увеличивает бремя поддержки самой библиотеки, и заставляет и иметь в штате специально обученных людей, которые будут принимать баги из региональных версий стандартной библиотеки, и переводить (или не переводить) их в термины глобальной стандартной библиотеки, если такая вообще имеется.
А что взамен — возможность чуть-чуть более знакомые имена методов применять?

Я не предлагаю расширять kotlin-stdlib, а добавить в число библиотек, поставляемых вместе с Котлином ещё одну. Она должна иметь статус как Ktor или Dokka. И иметь национальные варианты.

Это обещает расширить круг пользователей Котлина, включая студентов и школьников.

Школьникам и студентам совершенно ничто не помешает осваивать нелокализованную библиотеку. Программирование в широком смысле сводится просто к определённому набору символов, и 5.km/h понимается и запоминается ими ничуть не хуже, чем 5.км/ч.
В качестве бонуса, отсутствие локализации stdlib служит дополнительным вектором освоения иностранных языков.


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

К счастью, мы подходим к фиксации различий наших точек зрения.

На каком языке учить школьников - это отдельный вопрос.

В мире есть много стран со своими единицами измерений типа дюймы, фунты и т.д. Есть системы стандартов физических единиц в отраслях и даже у отдельных концернов.

Так что Вы думаете - нужна одна библиотека, а я - нужно много.

В мире есть много стран со своими единицами измерений типа дюймы, фунты и т.д. Есть системы стандартов физических единиц в отраслях и даже у отдельных концернов.

Так я не пойму, в чём проблема-то здесь, по-прежнему?


Unit<Length> METER = ...
Unit<Length> INCH = ...
Unit<Length> FOOT = ...

Unit<Weight> GRAM = ...
Unit<Weight> POUND = ...

val length = (1.METER + 15.INCH).to(FOOT);

val myValue = 1.METER + 1.POUND; // compilation error

Определять можно вообще любую единицу измерения, в любой системе стандартов — для этого понадобится не более и не менее чем определить, какая система "главная" (почему бы и не SI?), и по какому правилу происходит преобразование из одного в другое. А можно и не определять заранее, какая из систем предпочитается вообще, тогда и правил никаких не нужно будет.


Библиотека — это же не только сами эти вот метры и дюймы, они должны быть поверх чего-то ещё общего, которое будет иметь сами правила определения единиц, и правила их операций между собой.


Условно говоря, это несколько библиотек:


  • kt.measures
  • kt.measures.si (depends on kt.measures)
  • kt.measures.imperial (depends on kt.measures, optional dependency on kt.measures.si).

Ну и всё, как-то так.

Спасибо Вам за упоминание JSR 385. Я использовал это как один из аргументов в предложении расширить Колин функциональностью из KotUniL.

Это решение принято из прагматических соображений. А у Вас есть альтернативные предложения?

Много их. Посмотрите в сторону kmath

Хотя бы для денег Double не использовать.
Сложно сказать, как вам правильно будет: зависит от перспектив использования. Если нацелены на JVM, то BigDecimal неплохой вариант для денег. Если будут другие таргеты (JS, Native, KMM) то сходу не скажу. Но в них вы даже с спецсимволами в бэктиках помучаетесь.

Классическая проблема с деньгами - какой числовой тип выбрать. В KotUniL - каждая национальная валюта - это свой тип. Об этом - во второй статье серии.

Sign up to leave a comment.

Articles