Search code examples
schemeguileexpression-evaluation

Evaluating code which refers to function parameters


Update: The original version of this question did not fully describe the constraints of my situation. It now includes an example with some hand-waving for the sake of simplicity as well as a testable minimum example.

I am trying to pass a snippet of code into a procedure for evaluation at a later point in time. The system with all of the constraints looks like this:

; External Library code - cannot be changed
(define (serialize-parameters . args)
  (format some-file "~A~%" args))

(define (function-called-later-possibly-after-a-system-reboot callback)
  (apply callback some-default-parameters (deserialize-parameters some-file)))

; Internal library code - can be changed, but the value of `code-snippet` cannot be hardcoded
(define (callback some-default-parameters code-snippet)
  (eval code-snippet (current-module))

; Application code - can be changed, can be hardcoded
(define (main)
  (serialize-parameters #:code-snippet '(format #t "~A~%" some-default-parameters)))

The main point is to allow the application to execute an arbitrary code snippet in a callback function. The callback function receives other parameters which are not known to the application and may change between calls.

An important constraint is that the parameters that the application wants to send to the callback are written to a file and then read back at a later point in time, possibly after a system reboot.

This example is easier to test, but does not capture all of the constraints:

(define (test foo bar)
    (eval bar (current-module)))

(test "value of foo" '(format #t "~A~%" foo))

Running this program results in Unbound variable: foo. What I want is for the test function to be defined in such a way that the result of the call will be "value of foo\n" being printed to the terminal. Is this possible in Guile?

Thank you.


Solution

  • This will no work. eval takes an environment and you pass it (current-module). That are top level bindings in the module, like test but not any lexical bindings like foo or bar. They just don't exist in the environment returned by (current-module).

    You can do this:

    (define foo "value of foo")
    (eval '(format #t "~A~%" foo) (current-module))
    ; prints "value of foo"
    ; ==> #t
    

    Also, the elephant in the room is that you can do this with lambdas:

    (define (test value proc)
      (proc value))
    
    (test "value to process" (lambda (foo) (format #t "~A~%" foo)))
    ; prints "value to process"
    ; ==> #t
    

    Alternatively, but I'm guessing you can't have format in callback because "code-snippet" can have many different values:

    (define (main)
      (serialize-parameters #:code-snippet "~A~%"))
    
    (define (callback some-default-parameters code-snippet)
      (format #t code-snippet some-default-parameters))
    

    EDIT

    I think you can do it semi hard-coded:

    (define (main)
      (serialize-parameters #:code-snippet 'print-format))
    
    (define (callback argument message)
      (case message
        ((print-format) (format #t "~A~%" argument))
        ((other-message) (handle-message ...))
        (else ...)))
    

    You can even make it a dynamic dispatcher. Eg. you do something like this:

    (define messages '())
    (define (register-callback message proc)
      (set! messages (cons (cons message proc) messages)))
    
    (define (callback argument message)
      (let ((found (assq message messages)))
        (when found
          ((cdr found) argument))))
    
    (register-callback 'print-format (lambda (arg) (format #t "~A~%" arg)))
    
    (callback "test" 'print-format) ; prints "test"
    

    Now only the message gets stored in a file, which easily can be any data literal.