Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the reason for async.el say "Symbol's function definition is void" in Emacs Lisp

I use the async to call async function in elisp.

First of all I test the readme code in github and it works well.

Then I write a test code :

(defun async-function ()
    (async-start
          ;; What to do in the child process
          (lambda ()
                (shell-command-to-string "~/test.sh"))

          ;; What to do when it finishes
          (lambda (result)
                     (message "Async process done, result is: %s" result)))

And the test.sh code is very simple:

#! /usr/bin/env sh
sleep 2
echo "shell finish"

It works , But it failed when I change the lisp code like that :

;;;###autoload
(defun test ()
    (shell-command-to-string "~/test.sh"))

 (defun async-function ()
    (async-start
          ;; What to do in the child process
          (lambda ()
                (test))

          ;; What to do when it finishes
          (lambda (result)
                     (message "Async process done, result is: %s" result)))

The result is:

 error in process sentinel: Symbol's function definition is void: test

I use the autoload function to load the function file but the result tell me that the file can not found.

I have no idea what happen about that and how to fix.

like image 397
savior Avatar asked Apr 03 '14 15:04

savior


2 Answers

async.el works by spawning another Emacs process in which to evaluate the expressions. In this case you define the test function in your main Emacs process but the async Emacs process has no access to the functions and variables from your main process. If you want to specify functions for your async calls to use, put them in a file and load that file in your async function. But remember that variables and the like will not transfer over.

Here is a an example, this is in one file:

;; ~/.emacs.d/my-functions.el
(defun test ()
  (sit-for 2)
  "Hello, World!.")

And this is somewhere else:

;; somewhere else
(async-start
 (lambda ()
   (load-file "~/.emacs.d/my-functions.el")
   (test))
 (lambda (result)
   (message "result: %s" result))) ;; will message hello world

Regarding sharing variables, this is not supported by async. What you CANNOT do is start up an async function that continually modifies a variable and expect to have access to it on the main emacs process, or even just pass the variable into the async lambda, because the symbol won't be evaluated until it's on the new process where it won't exist.

We can make use of Lisp's backquote syntax to pass our variables into our async function by value. Below is a very hacky way of using a local variable and function in an async function.

;; We will declare a local variable `myvar'
(setq myvar "Bob")

;; Here is a simple function, notice that it does not
;; refer to other non standard functions or other local variables
(defun hello (name)
  (format "Hello, %s!" name))


(defun my-async-function ()
  (async-start
   ;; notice the backquote!
   `(lambda ()
      ;; in our async lambda we dont use the local `myvar' variable,
      ;; instead we are replacing it with the current local value of `myvar'
      (set  'myvar ,myvar)
      ;; we can also do this by actually obtaining the lambda expression
      ;; behind `hello' and putting that inside our lambda
      (fset 'hello ,(symbol-function 'hello))
      ;; then we wait!
      (sit-for 1)
      ;; now we are modifiying the async copy of `myvar' 
      (setq myvar (concat myvar " Johnson"))
      ;; here we want the result of the async lambda to be a call to our
      ;; `hello' function, but we also want to update our local version
      ;; of myvar with its value in th async process, so we will return
      ;; a list of both values which we can handle in our callback
      (list myvar (hello myvar)))

   (lambda (result)
     ;; once we get the results we'll update our local version of `myvar'
     ;; with the value returned by the async function
     (setq myvar (first result))
     ;; then we can print out the message we recieved from the output
     ;; of our async `hello' call.
     (message "The new value myvar is: %s\nThe result of the function was: %s"
          myvar
          (second result)))))


;; executed top down, the async callback will message:

;;The new value myvar is: Bob Johnson
;;The result of the function was: Hello, Bob Johnson!

I have abstracted out the concept of replacing variables and functions with their immediate value with a macro: value-bound-lambda, you can get the macro and see an example here:

value-bound-lambda example

like image 126
Jordon Biondo Avatar answered Oct 31 '22 13:10

Jordon Biondo


Just want to share a technique I use to run a custom function using async and not wanting to hard-code the file path. Assume I want to run the function my-custom-function that is in the file my-custom.el (i.e. "~/emacs.d/my-custom/my-custom.el"). So the symbol-file will return the filename where the function is defined and file-name-directory returns it's parent directory.

(let ((script-filename (file-name-directory (symbol-file 'my-custom-function))))
    (async-start
     `(lambda()
        (add-to-list 'load-path ,script-filename)
        (require 'my-custom)
        (my-custom-function)
    ....
like image 41
cjohansson Avatar answered Oct 31 '22 15:10

cjohansson