2

Let's say I have a list, for example: (define a '(+ (* p p) (* x x))).

How can I define a procedure with an expression given by a, for example here: (define (H x p) (+ (* p p) (* x x)))) ?

I tried to do something like: (define (H x p) (eval a)) but it says that p and x are undefined. I guess, there's a simple workaround with apply or something similar, but I can't wrap my head around it.

I guess I could just modify p and x in my list according to the value passed to the procedure H and then evaluate the new list, but it's kind of ugly... Or maybe there's a pretty way to do this?

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Syrocco
  • 179
  • 1
  • 5
  • Generally speaking the simplest approach is to not start from a list: just define your code as a lambda to begin with. There are rare, rare occasions when you need to interpret code as a list at runtime, but usually this desire stems from a misunderstanding of what "code is data" is actually about. Consider explaining the use case driving this before you go head-first into eval. – amalloy Jun 30 '21 at 03:49
  • I want to be able to write a mathematical function in the command prompt, like: 0.5 * p^2/m + 0.5 * k * x^2 , then I would use a procedure to convert this string into a list and write it under a form close to the one used in lisp (so something like (+ (/ (* p p) (* 2 m)) .....), but because of the conversion, my expression is a list... Maybe there's another way? – Syrocco Jun 30 '21 at 03:55
  • If you're already parsing a string into a list, why not just parse it into a lambda directly? – amalloy Jun 30 '21 at 04:24

2 Answers2

2

In R5RS Scheme (tested in Racket) the following works:

#lang r5rs

(define foo
  (eval (list 'lambda '(x p) '(+ (* p p) (* x x)))
        (scheme-report-environment 5)))

Then,

(foo 3 4)

;=> 25

Another option for eval is to use null-environment, but then + comes up undefined.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
2

Solution in Racket

What you try to do is actually to inject the pre-defined function body list construct into the body of a function definition (macro) call.

(define expr '(+ (* p p) (* x x)))

(eval `(define (fun x p) ,expr))

If you leave away the (eval ...) layer, you see:

`(define (fun x p) ,expr)
;; '(define (fun x p) (+ (* p p) (* x x)))

The code, which is actually evaluated by the eval.

Since this eval takes place in the global environment level, no side effects are to be feared.

Below are my more complicated solutions with a define-expr macro. They explain, why it was so difficult to solve. After all that, I saw that one needs actually only an (eval `(define (<args>) ,expr) construct and actually no additional macro.

Complicated Solution in Racket

You can do it in Racket, too:

(define expr '(+ (* p p) (* x x)))

(define-syntax-rule (define-expr args expr)
  (define args expr))

(eval `(define-expr (fun x p) ,expr))

This call executes in the background:

(define (fun x p) (+ (* p p) (* x x)))

What you try to do is actually a dynamic macro. The problem with this is that you have to give the function body code in runtime. Somehow you need to evaluate the function body expression by one evaluation more than the rest of the macro. Thus, it is necessary to wrap the macro call by an

(eval `<macrocall ,component-to-be-evaluated-once-more>)

I call this construct eval-over-macro-call.

After this you can call the so-defined fun function:

(fun 3 4) ;; 25

In common lisp

Personally, I prefer common lisp's macro system. it is more direct.

(defparameter *expr* '(+ (* p p) (* x x)))

(defmacro defun-expr (fnname args expr)
  `(defun ,fnname ,args ,expr))

(macroexpand-1 '(defun-expr fun (x p) *expr*)) ;; doesn't work
;; (DEFUN FUN (X P) *EXPR*) ;
;; T
;; you can see, that *EXPR* actually has to be evaluated once more

(macroexpand-1 `(defun-expr fun (x p) ,*expr*)) ;; this however is correct
;; (DEFUN FUN (X P) (+ (* P P) (* X X)))

;; Thus when you call the macro, you have to execute it using eval:
(eval `(defun-expr fun (x p) ,*expr*)) 
;; FUN ;; function is defined!

(fun 3 4) ;; 25

Since I am not that familiar with Racket's macro system, I did the macro construction in common lisp using the great macroexpand-1 which shows the code construct executed by the macro. And then transferred/guessed the corresponding define-syntax-rule in Racket.

In Racket, macroexpand-1 is (syntax->datum (expand-once '<macrocall>)):

(syntax->datum (expand-once `(define-expr (fun x p) ,expr)))
;; '(define (fun x p) (+ (* p p) (* x x)))
Gwang-Jin Kim
  • 9,303
  • 17
  • 30