This could be an interesting question; consider this clarification:
Evaluate list representing expression with free variable without using eval
What is a way to develop a function to evaluate a Scheme expression built from
the four arithmetic operations, numbers, and a free variable x, without using eval
?
The function is given a list representing the expression and a value for x.
Example: (calculate '(+ (* x 5) 2) 3)
=> 17
Development is presented as a sequence of elaborations of the calculate function;
each define
has a Signature comment on the same line, informally describing
argument/result types; function and following example(s) can be copy-pasted into a REPL.
Note: not all errors are detected; there is a compact version without comments at the end.
Getting started: write a function which works for the given example:
(define (calculate-0 expression-in-x value-for-x) ;; '(+ (* x 5) 2) Number -> Number
(if (equal? expression-in-x '(+ (* x 5) 2))
(+ (* value-for-x 5) 2)
(error #f "wrong expression" expression-in-x)))
(calculate-0 '(+ (* x 5) 2) 3) ;=> 17
Real function will have to extract pieces of expression
a simple example with all elements is '(+ x 1)
:
(define (calculate-1 expression value) ;; '(+ x <n>) Number -> Number
(let ([procedure (car expression)]
[argument-1 (cadr expression)]
[argument-2 (caddr expression)])
(if (and (eq? procedure '+)
(eq? argument-1 'x)
(number? argument-2))
(+ value argument-2)
(error #f "expression" expression))))
(calculate-1 '(+ x 1) 2) ;=> 3
+
in Scheme accepts any number of arguments, so replace if
with map/cond:
(define (calculate-2 expression value) ;; '(+ x|<n> ...) Number -> Number
(let ([arguments (cdr expression)])
(let ([arguments
(map (lambda (argument)
(cond ;; (compare with (if ...) in calculate-1)
[(eq? argument 'x) value ]
[(number? argument) argument ]
[else (error #f "argument" argument)]))
arguments)])
(apply + arguments))))
(calculate-2 '(+ 1 x) 2) ;=> 3
(calculate-2 '(+ x 1 x) 3) ;=> 7
(calculate-2 '(+) 99) ;=> 0
Get all four operations working:
(define (calculate-3 expression value) ;; '(op x|<n> ...) Number -> Number
(let ([procedure (car expression)]
[arguments (cdr expression)])
(let ([arguments ;; (same as calculate-2)
(map (lambda (argument)
(cond
[(eq? argument 'x) value ]
[(number? argument) argument ]
[else (error #f "argument" argument)]))
arguments)])
(apply (case procedure
[(+) + ]
[(-) - ]
[(*) * ]
[(/) / ]
[else (error #f "procedure" procedure)])
arguments))))
(calculate-3 '(* x 5) 3) ;=> 15
Allowing nested sub-forms needs just one small change:
(define (calculate-4 expression value) ;; '(op x|<n>|Expr ...) Number -> Number
(let ([procedure (car expression)]
[arguments (cdr expression)])
(let ([arguments
(map (lambda (argument)
(cond
[(eq? argument 'x) value ]
[(number? argument) argument ]
[(pair? argument) ;; (<- only change)
(calculate-4 argument value) ] ;;
[else (error #f "argument" argument)]))
arguments)])
(apply (case procedure
[(+) + ]
[(-) - ]
[(*) * ]
[(/) / ]
[else (error #f "procedure" procedure)])
arguments))))
(calculate-4 '(+ (* x 5) 2) 3) ;=> 17
So there it is: try calculate-4 with the original example in the REPL:
$ scheme
> (calculate-4 '(+ (* x 5) 2) 3)
17
> ; works with all Scheme Numbers:
(calculate-4 '(+ (* x 15/3) 2+2i) 3.0)
17.0+2.0i
>
Not so fast ... expression
is a list with the form of a Scheme expression using four
operations, Numbers, and x. But the question doesn't require value
to be a Number: procedures are
first-class values in Scheme
(expression could be '(+ (x 3) 2)
with value (lambda (n) (* n 5))
):
(define (calculate-5 expression value) ;; '(x|op x|<n>|Expr ...) Number|Lambda -> Value
(let ([procedure (car expression)]
[arguments (cdr expression)])
(let ([arguments
(map (lambda (argument)
(cond
[(eq? argument 'x) value ]
[(number? argument) argument ]
[(pair? argument) (calculate-5 argument value) ]
[else (error #f "argument" argument)]))
arguments)])
(let ([procedure
(cond ;; (compare with argument cond above)
[(eq? procedure 'x) value ]
[(pair? procedure) (calculate-5 procedure value)]
[else (case procedure
[(+) + ]
[(-) - ]
[(*) * ]
[(/) / ]
[else (error #f "procedure" procedure)]) ]) ])
(apply procedure arguments)))))
(calculate-5 '(+ (x 3) 2) (lambda (n) (* n 5))) ;=> 17
(And so, finally, our calculate
function is "Hello World!" capable :)
$ scheme
> ;(copy-paste calculate-5 here)
> (calculate-5 '(x) (lambda _ 'Hello!))
Hello!
>
Compact version (returns #f on error):
(define (calculate expr value)
(call/cc (lambda (error)
(let* ([proc (car expr)]
[args (map (lambda (arg) (cond
[(eq? arg 'x) value]
[(number? arg) arg]
[(pair? arg)
(or (calculate arg value) (error #f))]
[else (error #f)]))
(cdr expr))]
[proc (cond
[(eq? proc 'x) value ]
[(pair? proc) (calculate proc value)]
[else (case proc [(+) +] [(-) -] [(*) *] [(/) /]
[else (error #f)])])])
(apply proc args)))))