Современные вычисления всё чаще сталкиваются с необходимостью оперировать не только точными значениями, но и учитывать диапазоны вероятных значений, отражающих неопределённость или вариативность исходных данных. В этой статье мы рассмотрим, как можно реализовать калькулятор, способный работать с такими диапазонами неопределённости, используя язык программирования Haskell и при этом укладываясь всего в 100 строк кода. Данный подход базируется на идее добавления оператора диапазона ~, позволяющего задавать значения в виде интервалов с доверительной вероятностью. Идея калькулятора с неуверенными значениями довольно проста, но мощна. Вместо того чтобы оперировать конкретными числами, мы можем задать диапазон, например 10~15, что означает: "число находится в диапазоне от 10 до 15 с вероятностью 95%".
Подобное расширение синтаксиса позволяет естественно выражать неопределённость, а благодаря функциональной природе Haskell, такие диапазоны легко комбинировать и обрабатывать. Для представления самой вычислительной модели используется упрощённый вероятностный монад Dist. Этот монад представляет либо конкретное значение, либо вычисление с распределением по нормальному закону. Основные конструкторы в Dist — Return, который оборачивает конкретное значение; Bind, позволяющий создавать цепочки зависимых вычислений; и Normal, задающий нормальное распределение с заданными средним значением и стандартным отклонением. Благодаря реализации монадических интерфейсов, можно использовать удобный синтаксис do-нотации для построения сложных выражений.
Для генерации значений из вероятностного дистрибутива применяется стандартный генератор случайных чисел StdGen. Основным приёмом является трансформация равномерного распределения в нормальное с помощью трансформации Бокса–Мюллера, что является классическим методом для получения нормально распределённых случайных чисел на основе пар равномерных чисел. При вычислении композиции распределений происходит разделение генератора случайных чисел, чтобы сохранить независимость разных случайных величин, что важно для корректности симуляции. Далее для построения вычислений создаётся внутренний язык домена (eDSL) на основе типа Expr. Этот тип включает в себя базовые арифметические операции, математические функции, а также новый оператор Range, обозначаемый символом ~.
Благодаря перегрузке операторов через классы типов Num, Fractional и Floating, пользователь способен без труда комбинировать точные и диапазонные значения, как если бы это были обыкновенные числа. Таким образом выражения, содержащие неопределённые диапазоны, выглядят естественно и читаемо. Ключевая часть — реализация функции eval, которая переводит выражение Expr в вероятностный монад Dist Double. Для обычных операций эта функция рекурсивно вычисляет распределения аргументов и соединяет их с математическими операциями. Особенно интересен случай Range, когда на основе крайних значений вычисляется нормальное распределение с центром в середине диапазона и стандартным отклонением, соответствующим ширине этого промежутка.
Такой подход учитывает статистики и позволяет работать с неопределённостью в вычислениях корректно и последовательно. Что же получается на выходе? После определения выражения и его перевода в Dist, мы можем многократно случайно взять значения из этого распределения. Это позволяет не столько получить одно итоговое число, сколько визуализировать распределение результатов и оценить вероятность различных исходов. Для удобства восприятия реализована функция, которая группирует множество случайных значений в интервалы, считая относительную частоту попадания в каждый из них. На основе этих данных строится текстовая гистограмма, отображающая плотность распределения в удобном для пользователя виде.
Например, выражение 1400~1700 * 0.55~0.65 - 600~700 - 100~200 - 30 - 20, которое сочетает несколько диапазонов неопределённости, приводит к распределению итоговых результатов с определённой формой и вариативностью. При выводе на экран это выглядит как красивый гистограммный график, который позволяет визуально понять, какие результаты более вероятны, а какие — менее. Такой подход на практике раскрывает преимущества функционального программирования для задач, связанных с вероятностями и неопределённостью.
Благодаря свободному определению видов выражений и ёмкой реализации монад, в рамках небольшого и компактного кода можно решать задачи, которые традиционно требуют громоздких специализированных библиотек. Использование Haskell для подобных разработок открывает и дополнительные возможности. В частности, интеграция с GHCi позволяет интерактивно экспериментировать с выражениями, определять переменные, функции и не бояться «ломать» систему — ошибки легко отслеживать благодаря типовому контролю и чистому функциональному подходу. Вы спокойно можете создавать сложные композиции неуверенных чисел, быстро тестировать их поведение и анализировать результаты. Конечно, модель монад Dist и используемое нормальное распределение — упрощения в сравнении с полноценными системами вероятностного программирования.
Она не учитывает условные вероятности и не вычисляет аналитически характеристики распределений, полагаясь на многоразовую сэмплирование. Тем не менее этот компромисс позволяет сохранять простоту и компактность кода, что улучшает читаемость и облегчает обучение. Важно отметить, что выбранная схема задания диапазона с помощью нормального распределения подразумевает, что границы интервала расположены на расстоянии примерно двух стандартных отклонений от среднего. Это делает распределение сфокусированным в пределах именно указанного диапазона, но с плавными хвостами, что характерно для реальных измерений, где точные границы редко достижимы. Для разработчиков, заинтересованных в развитии подобной системы, открывается простор для расширения функциональности.
Можно добавить поддержку других типов распределений, учитывать корреляции между переменными, интегрировать визуализации в графический интерфейс, или же оптимизировать процесс сэмплирования для повышения производительности. Всё это возможно благодаря модульной структуре и гибкости Haskell. Не менее интересно, что такой калькулятор способен применять себя в практических задачах финансового анализа, оценки рисков, научных вычислений с погрешностями или инженерных расчётов, где неопределённость — важнейшая составляющая. В отличие от классических калькуляторов, которые работают только с точными числами, данный подход даёт понимание распределения конечного результата и позволяет принимать решения, основанные на вероятностях. В итоге, реализация Unsure Calculator на Haskell — это отличный пример сочетания чистоты функционального программирования с реальными задачами моделирования неопределённости.
Компактный и понятный код демонстрирует, что мощные концепции не должны сопровождаться сложностью, а грамотное проектирование позволяет получать полезные инструменты даже в ограниченных ресурсах строк кода. Если вы желаете познакомиться глубже с примерами или сразу попробовать работу с подобным калькулятором, можно легко загрузить код в GHCi и начать играться с выражениями, экспериментируя с различными диапазонами значений. Такой интерактивный опыт помогает лучше понять природу вероятностей и вдохновляет на создание собственных расширений и проектов в области вероятностного программирования.