Search code examples
clojureevalread-eval-print-loop

Clojure's eval does not "see" local symbols


I am experimenting with eval in Clojure:

(let [code_as_data '(if (< sequ) on_true on_false)
      sequ [1 3 5]
      on_true "sequence is sorted in ascending order"
      on_false "sequence is NOT sorted"]
  (eval code_as_data))

CompilerException java.lang.RuntimeException: Unable to resolve symbol: sequ in this context, compiling:(/tmp/form-init3253735970468294203.clj:1:25)

How do I define symbols so that they are "seen" by eval?


Solution

  • The simplest way to provide local data to code generated at runtime by eval is to generate a form that takes arguments.

    (let [code-as-data '(fn [sequ on-true on-false]                                 
                          (if (apply < sequ)                                        
                            on-true                                                 
                            on-false))                                              
          f (eval code-as-data)]                                                    
      (f [1 3 5]                                                                    
         "sequence is sorted in ascending order"                                    
         "sequence is NOT sorted"))
    

    Of course, since functions are our standard means of inserting runtime values into known forms, this really doesn't need to use eval at all. The same functionality can be expressed more simply without eval:

    (let [f (fn [sequ on-true on-false]                                             
              (if (apply < sequ)                                                    
                on-true                                                             
                on-false))]                                                         
      (f [1 3 5]                                                                    
         "sequence is sorted in ascending order"                                    
         "sequence is NOT sorted"))
    

    In actual code, the eval version is only needed if the logic needs to be generated at runtime (for example if a user provides a new algorithm). If it is onerous to expect users to write their code as a function, you can do a compromise:

    (defn code-with-context                                                         
      [body sq t else]                                                              
      (let [f (eval (list 'fn '[sequ on-true on-false] body))]                      
        (f sq t else)))                                                             
    
    
    
    (code-with-context (read-string "(if (apply < sequ) on-true on-false)")         
                       [1 3 5]                                                      
                       "sequence is sorted in ascending order"                      
                       "sequence is NOT sorted")