"Да какой ты на[фиг] оптимизатор? хать, тфу."
Проблема
Пришёл в новый проект где было подключено три математические библиотеки. Кодовая база впитала опыт нескольких команд разработчиков и представляла типичный legacy где обосновать то или иное решение не всегда представляется возможным. Дойдя в бэклоге до оптимизации я стал перебирать варианты того, как это можно решить. В результате своих размышлений я пришёл к выводу, что хочу сохранить все имеющиеся библиотеки, но убрать с глаз разницу в API. Желаю конфигурировать на лету "математический движок" в одной точке (не залезая в, непосредственно, сам клиентский код).
Решение
Синтаксис математических выражений основанный на массивах (назвал nemathode и создал под него отдельную страничку со всей информацией которая поможет втянуться в этот механизм). Я хотел, чтобы он был максимально близок к большинству популярных языков, но в первую очередь к JavaScript.
То есть, сильно ничего не меняется:
круглые скобки меняются на квадратные;
операторы (константы, имена функций) обрамляются в кавычки;
а конфигурация происходит в одной точке (до которой мы скоро дойдём).
Применив эту штуку я теперь могу:
продолжать использовать кучу математических библиотек и конфигурировать их в одной точке скрывая детали API
добавлять новые методы, операторы, константы и выбирать удобное именование;
калибровать точность и скорость вычислений на лету (корректировать текущие имплементации, заменять математические константы с более подходящими значениями).
Примеры использования
Бинарные операторы
Функции
Математические константы
Вложенные выражения
Пример конфигурации
Больше примеров настройки библиотеки можно найти здесь
mathConstants
Буквально, математические константы
functions
Набор имплементаций
toInputType
Это кажется избыточным, но не все используют функциональность чистого JS. Существует ряд библиотек где промежуточным типом в вычислениях не является число (bignumber.js, decimal.js). Таким образом мы можем настроить этот обработчик как на примере ниже
toOutputType
Противоположен toInputType. Смотрите пример ниже
binaryOperators
API - стандартное
API - с любовью ❤️
Послесловие
Вдохновлялся
Вопросом на stackoverflow
Несколькими задачами на codewars (не помню названия)
math.js библиотекой и её механизмом вычислений
В будущем (если решение пригодится не только мне)
Полное TS покрытие (чтобы был автокомплит по операторам при написании выражений)
Унарные операторы (до сих пор сомневаюсь по поводу их необходимости. Нужна Ваше мнение)
Обработка аргументов функции
Преодолеть ограничения связанные с возвращаемыми типами значений (number, boolean etc)
И напоследок
Если у Вас есть вопросы, предложения, пожелания, критика - я буду рад услышать обратную связь в любой форме. Ссылка на вебсайт со всей необходимой информацией.
Комментарии (15)
Hrodvitnir
18.07.2022 07:17+5Не до конца, видимо, понял зачем это нужно
Вычисление будет в разы дольше нативного из-за парсинга, а запрограммировать приоритеты операторов можно только так же как в стандартной математике, иначе вы всех введете в заблуждениеТак что вышел yetAnotherFramework
kas_elvirov Автор
18.07.2022 09:39+1Приоритеты можно задать на своё усмотрение. По поводу скорости, да. Думаю над ускорением этого процесса
kas_elvirov Автор
18.07.2022 09:44А по поводу скоростей, вычислений не так много и прирост в скорости не так сильно заметен )
Flux
18.07.2022 07:42У вас во фронтенде правда всё настолько весело что вместо того чтобы писать арифметические выражения средствами языка вы пишете отдельный фреймворк с красивым названием и интересной иконкой?
С
evaluatePlease
отдельно проиграл, конечно. Уже предусмотрена возможность в случае чего извиниться перед фреймворком замисгендерингопечатку в имени метода?kas_elvirov Автор
18.07.2022 09:43-1Речь идёт про вычисления в JS.
А история создания упирается в вынос вычислений (С бэка) на фронт которые были реализованы при помощи нескольких библиотек. Этот скрипт (выше), действительно), помог справиться с унификацией вычислений
Alexandroppolus
18.07.2022 13:28Выражения в виде массивов - это необходимость, или для удобства?
Просто если бы выражения были в виде строк, то можно было бы воспользоваться конструктором Function, который, будучи лишен недостатков eval, позволяет в том числе передать параметры и вспомогательные функции/константы
Saiv46
18.07.2022 14:07-1Строки все равно придется парсить в AST, перед этим преобразовав в массив, также что это скорее для удобства.
PS. По заголовку сначала подумал что статья будет про использование математики на российском фронте во время сами-знаете-чего.
Riim
18.07.2022 16:20Делал что-то подобное для Decimal.js, сложные записи на котором в некоторых местах совсем уж тяжко стало читать, а производительностью можно было пожертвовать в пользу читабельности. Но я пошёл чуть другим путём -- использовал tagged template literals, в простейшем случае это выглядело примерно так:
big`(${x} + ${y}) * ${z}`
Парсится такой вариант чуть дольше конечно, но это в любом случае не для повсеместного использования. Вариант с массивом помню тоже рассматривал, но сейчас уже и не вспомню почему отказался.
Fen1kz
Либо я неправильно понял зачем это, либо здесь нужна картинка из xkcd про стандарты
kas_elvirov Автор
нагуглил только комикс. Его имеете ввиду?)
Fen1kz
Да. Чтобы избавиться от 3 библиотек, вы сделали 4 библиотеку и теперь следующему за вами придется избавляться от 4 библиотек
13luck
Тоже не понимаю. Вы выбрали запись массивами в угоду удобства сериализации данных вместо человеко-читаемости? Как-будто, можно найти решение через описание грамматики для парсеров (PEG), оставив при этом простую и понятную запись. У Алексея Охрименко есть хороший доклад на эту тему.