0

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.

1 Answers1

2

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.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • But the system that I am working with doesn't define the variable I want globally, it gives me a callback function and passes in the argument as a parameter. So I need to refer to the parameter in the code snippet. – Skyler Ferris Feb 01 '20 at 21:53
  • Unfortunately, I cannot use a lambda because I cannot change some of the code and because the data is written to and then read from a file before being used, and procedures are not `read`able. The OP has been updated with additional details. Thank you. – Skyler Ferris Feb 01 '20 at 22:41
  • @saffronsnail I have no idea what you mean by "can be changed, cannot be hardcoded", but since you can change both `main` and `callback` it seems plausible that it can be done with either a `lambda` or just passing the format first parameter. `some-default-parameters` cannot be plural or it has to be a list perhaps so egven if your plan worked you'd still have problems if it rest arged. – Sylwester Feb 01 '20 at 23:32
  • What I mean by "cannot be hardcoded" is that I can't just put the `format` statement directly into the callback because different applications rely on the function and they have to do different things. – Skyler Ferris Feb 01 '20 at 23:39
  • It can't be done with a lambda because when the external library writes the `lambda` to a file in uses the syntax `#` which causes an error when it tries to read it back later. – Skyler Ferris Feb 01 '20 at 23:40
  • The semi-hardcoded approach is probably what I need to do. I like the dynamic dispatch in that it is more elegant, but I'm not guaranteed to be in the same process when the callback is called. – Skyler Ferris Feb 03 '20 at 16:22
  • Thank you for your time. – Skyler Ferris Feb 03 '20 at 16:23