Добавление новых операторов

Чем ещё хороша наша функция – её можно легко модифицировать для поддержки других операторов. Операторы не обязательно должны быть бинарными. Например, мы можем создать оператор log, который выталкивает из стека одно число и заталкивает обратно его логарифм. Также можно создать тернарный оператор, который будет извлекать из стека три числа и помещать обратно результат. Или, к примеру, реализовать оператор sum, который будет поднимать все числа из стека и суммировать их.

Давайте изменим нашу функцию так, чтобы она понимала ещё несколько операторов.

solveRPN :: String –> Double

solveRPN = head . foldl foldingFunction [] . words

   where

      foldingFunction (x:y:ys) "*" = (x * y):ys

      foldingFunction (x:y:ys) "+" = (x + y):ys

      foldingFunction (x:y:ys) "–" = (y – x):ys

      foldingFunction (x:y:ys) "/" = (y / x):ys

      foldingFunction (x:y:ys) "^" = (y ** x):ys

      foldingFunction (x:xs) "ln" = log x:xs

      foldingFunction xs "sum" = [sum xs]

      foldingFunction xs numberString = read numberString:xs

Прекрасно. Здесь / – это, конечно же, деление, и ** – возведение в степень для действительных чисел. Для логарифма мы осуществляем сравнение с образцом для одного элемента и «хвоста» стека, потому что нам нужен только один элемент для вычисления натурального логарифма. Для оператора суммы возвращаем стек из одного элемента, который равен сумме элементов, находившихся в стеке до этого.

ghci> solveRPN "2.7 ln"

0.9932517730102834

ghci> solveRPN "10 10 10 10 sum 4 /"

10.0

ghci> solveRPN "10 10 10 10 10 sum 4 /"

12.5

ghci> solveRPN "10 2 ^"

100.0

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

ПРИМЕЧАНИЕ. Как можно заметить, функция не устойчива к ошибкам. Если передать ей бессмысленный вход, она вывалится с ошибкой. Мы сделаем её устойчивой к ошибкам, определив её тип как solveRPN :: String –> Maybe Double, как только разберёмся с монадами (они не страшные, честно!). Можно было бы написать безопасную версию функции прямо сейчас, но довольно-таки скучным будет сравнение с Nothing на каждом шаге. Впрочем, если у вас есть желание, попробуйте! Подсказка: можете использовать функцию reads, чтобы проверить, было ли чтение успешным.