Понимание пути Clojure

Мне действительно интересно стать опытным в Clojure/Clojurescript для веб-приложений. Прямо сейчас я делаю простые приложения командной строки, чтобы почувствовать язык.

Но сейчас трудно понять, как добиться успеха в языке без изменяемых переменных.

Моя проблема: я делаю небольшой калькулятор RPN, в котором пользователь может вводить числа для добавления в стек, а также выполнять математические операции со стеком:

> ;adding to stack
> 4 4
> ; print the stack
> [4, 4]
> 2 3
> p
> [4 4 2 3]
> ; adding the top two items to the stack
> +
> p
> [4 4 5]
> + -
> p
> [-5]

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

2 ответа

Решение

Хотя это работает, я бы избегал подхода Atom/ Ref, поскольку на самом деле это не "Clojure way". (Они являются правильными инструментами для решения некоторых проблем, но не этой). Подход стекового фрейма намного больше соответствует философии Clojure.

Чтобы расширить решение стекового фрейма, применение каждой операции будет такой функцией:

new-stack (apply-op stack op)

Операции типа + и - сделают очевидное, в то время как 'p' будет иметь побочный эффект (IO), но в противном случае вернет исходный стек.

Цикл тогда тривиален:

(loop [stack [] op (get-op!)]
    (if (not= 'q op)
        (recur (apply-op stack op) (get-op!))))

Я предполагаю символ 'q' в качестве команды завершения и get-op! быть операцией чтения.

Также стоит отметить, что "стек" более естественно реализован с помощью списка, так как необходимые операции first/rest/cons уже доступны. Например, применение любого бинарного оператора к основанному на списке стеку просто:

(cons (the-operator (first stack) (second stack)) (rest (rest stack)))

Или используя деструктуризацию для ясности:

(let [[a b & r] stack] (cons (the-operator a b) r))

Использование вектора в качестве стека не так просто и не эффективно.

Вы можете структурировать его способом, подобным Java, с атомом или ref, чтобы отслеживать текущий стек или реструктурировать проблему как рекурсивный цикл, который "хранит" текущий стек в пределах стека вызовов. Последнее решение может использовать много стековых фреймов, поэтому вы можете использовать recur предотвратить переполнение стека.

Другие вопросы по тегам