"Да какой ты на[фиг] оптимизатор? хать, тфу."

Проблема

Пришёл в новый проект где было подключено три математические библиотеки. Кодовая база впитала опыт нескольких команд разработчиков и представляла типичный legacy где обосновать то или иное решение не всегда представляется возможным. Дойдя в бэклоге до оптимизации я стал перебирать варианты того, как это можно решить. В результате своих размышлений я пришёл к выводу, что хочу сохранить все имеющиеся библиотеки, но убрать с глаз разницу в API. Желаю конфигурировать на лету "математический движок" в одной точке (не залезая в, непосредственно, сам клиентский код).

Решение

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

То есть, сильно ничего не меняется:

  • круглые скобки меняются на квадратные;

  • операторы (константы, имена функций) обрамляются в кавычки;

  • а конфигурация происходит в одной точке (до которой мы скоро дойдём).

Схематично о смысле синтаксиса
Схематично о смысле синтаксиса

Применив эту штуку я теперь могу:

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

  2. добавлять новые методы, операторы, константы и выбирать удобное именование;

  3. калибровать точность и скорость вычислений на лету (корректировать текущие имплементации, заменять математические константы с более подходящими значениями).

Примеры использования

Бинарные операторы

Функции

Математические константы

Вложенные выражения

Пример конфигурации

Больше примеров настройки библиотеки можно найти здесь

mathConstants

Буквально, математические константы

functions

Набор имплементаций

toInputType

Это кажется избыточным, но не все используют функциональность чистого JS. Существует ряд библиотек где промежуточным типом в вычислениях не является число (bignumber.js, decimal.js). Таким образом мы можем настроить этот обработчик как на примере ниже

toOutputType

Противоположен toInputType. Смотрите пример ниже

binaryOperators

API - стандартное

API - с любовью ❤️

Послесловие

Вдохновлялся

  1. Вопросом на stackoverflow

  2. Несколькими задачами на codewars (не помню названия)

  3. math.js библиотекой и её механизмом вычислений

В будущем (если решение пригодится не только мне)

  1. Полное TS покрытие (чтобы был автокомплит по операторам при написании выражений)

  2. Унарные операторы (до сих пор сомневаюсь по поводу их необходимости. Нужна Ваше мнение)

  3. Обработка аргументов функции

  4. Преодолеть ограничения связанные с возвращаемыми типами значений (number, boolean etc)

И напоследок

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

Комментарии (15)


  1. Fen1kz
    18.07.2022 02:00
    +1

    Либо я неправильно понял зачем это, либо здесь нужна картинка из xkcd про стандарты


    1. kas_elvirov Автор
      18.07.2022 09:38
      -2

      нагуглил только комикс. Его имеете ввиду?)


      1. Fen1kz
        18.07.2022 14:51

        Да. Чтобы избавиться от 3 библиотек, вы сделали 4 библиотеку и теперь следующему за вами придется избавляться от 4 библиотек


      1. 13luck
        19.07.2022 18:49

        Тоже не понимаю. Вы выбрали запись массивами в угоду удобства сериализации данных вместо человеко-читаемости? Как-будто, можно найти решение через описание грамматики для парсеров (PEG), оставив при этом простую и понятную запись. У Алексея Охрименко есть хороший доклад на эту тему.


  1. Hrodvitnir
    18.07.2022 07:17
    +5

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

    Так что вышел yetAnotherFramework


    1. kas_elvirov Автор
      18.07.2022 09:39
      +1

      Приоритеты можно задать на своё усмотрение. По поводу скорости, да. Думаю над ускорением этого процесса


    1. kas_elvirov Автор
      18.07.2022 09:44

      А по поводу скоростей, вычислений не так много и прирост в скорости не так сильно заметен )


  1. Flux
    18.07.2022 07:42

    У вас во фронтенде правда всё настолько весело что вместо того чтобы писать арифметические выражения средствами языка вы пишете отдельный фреймворк с красивым названием и интересной иконкой?


    С evaluatePlease отдельно проиграл, конечно. Уже предусмотрена возможность в случае чего извиниться перед фреймворком за мисгендеринг опечатку в имени метода?


    1. kas_elvirov Автор
      18.07.2022 09:43
      -1

      Речь идёт про вычисления в JS.

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


  1. Beholder
    18.07.2022 11:50
    +3

    "Мы встроили интерпретатор в твой интерпретатор..."


  1. Alexandroppolus
    18.07.2022 13:28

    Выражения в виде массивов - это необходимость, или для удобства?

    Просто если бы выражения были в виде строк, то можно было бы воспользоваться конструктором Function, который, будучи лишен недостатков eval, позволяет в том числе передать параметры и вспомогательные функции/константы


    1. Saiv46
      18.07.2022 14:07
      -1

      Строки все равно придется парсить в AST, перед этим преобразовав в массив, также что это скорее для удобства.

      PS. По заголовку сначала подумал что статья будет про использование математики на российском фронте во время сами-знаете-чего.


      1. Alexandroppolus
        18.07.2022 14:39

        Строки все равно придется парсить в AST

        Зачем?


  1. Riim
    18.07.2022 16:20

    Делал что-то подобное для Decimal.js, сложные записи на котором в некоторых местах совсем уж тяжко стало читать, а производительностью можно было пожертвовать в пользу читабельности. Но я пошёл чуть другим путём -- использовал tagged template literals, в простейшем случае это выглядело примерно так:

    big`(${x} + ${y}) * ${z}`
    

    Парсится такой вариант чуть дольше конечно, но это в любом случае не для повсеместного использования. Вариант с массивом помню тоже рассматривал, но сейчас уже и не вспомню почему отказался.


  1. AlexSpaizNet
    20.07.2022 13:40

    Может сразу в польскую нотацию?

    https://ru.wikipedia.org/wiki/Польская_запись