Search code examples
bindingracketeval

Local bindings for Racket's eval function


I am implementing a genetic programming application in which arithmetic expressions, containing safe-div and safe-sqrt, are dynamically generated by a function (as lists). To calculate the fitness of these expressions, I need to use eval to turn them into executable functions. However, since Racket's eval cannot see local bindings in the context where it is called, I did the following:

#lang racket

(define (make-lambda args expression)
  (eval `(let ([safe-div  (lambda (x y) (if (= y 0) 1 (/ x y)))]
               [safe-sqrt (lambda (x)   (sqrt (abs x)))])
           (lambda ,args ,expression))
        (make-base-namespace)))

(writeln ((make-lambda '(x y) '(+ x (safe-sqrt  y))) 2 -9)) ; writes 5
(writeln ((make-lambda '(x y) '(+ x (safe-div y 0))) 2 -9)) ; writes 3

That works! However, I would like to know if there is a better alternative to solve this problem.


Solution

  • You can use a sandbox to define an evaluation environment that provides your safe functions using make-evaluator:

    #lang racket/base
    
    (require racket/sandbox)
    
    ; Kebab-case, not snake_case!
    (define safe-evaluator
      (make-evaluator
       'racket/base
       '(define (safe-div x y)
          (if (= y 0) 1 (/ x y)))
       '(define (safe-sqrt x)
          (sqrt (abs x)))))
    
    (safe-evaluator '((lambda (x y) (+ x (safe-sqrt y))) 2 -9))
    (safe-evaluator '((lambda (x y) (+ x (safe-div y 0))) 2 -9))
    

    You can also use sandboxes to add restrictions like how much time or memory should be used evaluating things and other fine tuning about its permissions and such if you're really worried about safety.


    If there's a lot of functions, it might be better to organize things by putting them all in a module of their own and requiring it:

    (module safe-functions racket/base
      (provide safe-div safe-sqrt)
      (define (safe-div x y)
          (if (= y 0) 1 (/ x y)))
      (define (safe-sqrt x)
          (sqrt (abs x))))
    
    (define safe-evaluator
      (make-evaluator 'racket/base #:requires '((submod "eval.rkt" safe-functions))))
    

    (Replace "eval.rkt" with the name of your source file, or if using a top level module instead, the whole submod expression with its name just like you'd do for a normal require)