______________________ RPN CALCULATOR REDUX lro ______________________ <2019-01-03 Thu> Table of Contents _________________ Stuff we can re-use Values Push dup factorial rpn-func macro Main loop conclusion In my last phlog, I wrote a simple RPN calculator in scheme, but even though it wasn't particularly "functional", the stack was a global variable that we modified in place. Not good form a functional point of view. So lets rewrite our calculator to be more functional. It can't really be purely functional because scheme isn't purely functional itself. But we can make our best effort to be as functional as possible. Stuff we can re-use =================== We can't keep alot of our old implementation, we just have to change the functions that alter the stack to return the altered stack instead, and also take the stack as an argument. Like /pop!/ use to just alter the stack using /set!/, but insetad /pop/ will take the stack as its argument and return the popped variable and the stack. ,---- | (define (pop stack) | (let ((var (car stack)) | (ret-stack (cdr stack))) | (values var ret-stack))) `---- I could have just one-linered this but its easier to see what is going on if i use a /let/ and name the return values nicely. Values ====== If you know how multiple return values work in scheme, then you can skip this section. But otherwise here we go! You can do multiple return values in scheme several ways, the easiest is probably to build a list and return the list where each element is one of the return values. ,---- | (define (pop stack) | (let ((var (car stack)) | (ret-stack (cdr stack))) | (list var ret-stack))) `---- But when accessing each variable, it requires some list dancing to access but is my personal usual way of handling multiple return values, but i'm choosing to go with /values/ this time because I haven't used it before. And the third is continuation passing style, which is strange looking. You return a function acting on your return variables that was passed to your function. ,---- | (define (pop stack func) | (let ((var (car stack)) | (ret-stack (cdr stack))) | (func var ret-stack))) `---- So this time around I've chosen to go with /values/. Push ==== Pushing values onto to stack is pretty much the same, we just return the modified stack and thats it. ,---- | (define (push var stack) | (append (list var) stack)) `---- dup === dup duplicates the last item on the stack, and is again similar to /push/ where all we do is take the stack as an argument and return it again. ,---- | (define (dup stack) | (let ((head (car stack))) | (append (list head) stack))) `---- factorial ========= Factorial is exactly the same. ,---- | (define (fact x) | (define (fact-iter n current) | (if (= n 1) | current | (fact-iter (- n 1) (* n current)))) | (fact-iter x 1)) `---- rpn-func macro ============== We will have to change this macro so that it returns the stack and also uses the new /pop/ and /push/. I will also change it just a function because it's not really a good use for a macro. So you can see I used /let*-values/ to capture the return values of popping the stack twice, and because we used /let*-values/ instead of /let-values/, we can use the previous varibles so that the modified stack can be passed to the next /pop/. ,---- | (define (rpn-func func stack) | (let*-values (((var1 stack) (pop stack)) | ((var2 stack) (pop stack))) | (push (func var1 var2) stack))) `---- Main loop ========= The main loop doesn't need too much changing, but there are some things we need to change. 1. Initial call to loop needs another variable, the stack. 2. change all the /rpn-func/ calls to use the stack. 3. change the implementation of modulo, factorial etc 4. change $ to use /let-values/ to display the top element of the stack then return. The only problem is how do we get what is returned form the /cond/ to our loop invocation. One way is to have every expression in the cond call /loop/, but that seems ugly to me. If I find a better solution later I will phlog about it. ,---- | (let loop ((input (read)) (stack '())) | (cond | ((number? input) (loop (read) (push input stack))) | ((eq? '+ input) (loop (read) (rpn-func + stack))) | ((eq? '- input) (loop (read) (rpn-func - stack))) | ((eq? '* input) (loop (read) (rpn-func * stack))) | ((eq? '/ input) (loop (read) (rpn-func / stack))) | ((eq? '% input) (loop (read) (rpn-func modulo stack))) | ((eq? '! input) (loop (read) | (let-values (((var stack) (pop stack))) | (push (fact var) stack)))) | ;;((eq? '^ input) (loop (read) (rpn-func pow))) | ((eq? '$ input) (loop (read) | (let-values (((var stack) (pop stack))) | (display var) | (newline) | stack))) | ((eq? 'dup input) (loop (read) (dup stack))) | (else (begin | (display "ERROR not valid input: ") | (display input) | (newline) | (loop (read) stack))))) `---- conclusion ========== So there we are, a mostly more functional RPN calculator, though there is still some more improvements that can be made.