emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: continuation passing in Emacs vs. JUST-THIS-ONE


From: Tomas Hlavaty
Subject: Re: continuation passing in Emacs vs. JUST-THIS-ONE
Date: Sun, 26 Mar 2023 21:35:27 +0200

On Sat 25 Mar 2023 at 19:42, Tomas Hlavaty <tom@logand.com> wrote:
>> I don't think so, no.  But you would need fancy rewriting if you wanted
>> to allow
>>
>>     (concat foo (futur-let* (...) ...))

or one could do it explicitly:

   (concat foo (future-wait (futur-let* (...) ...)))

> Why do you recommend to poll with futur.el?

I see now that it is future-wait which requires it.

I think I managed to derive nicer async and await than futur.el:

Here a future is just a thunk which returns the resolved value, EAGAIN
if unresolved or throws an error.

(defun await (future)
  (let (z)
    (while (eq 'EAGAIN (setq z (funcall future)))
      ;; TODO poke sit-for/io on yield?  how?
      (sit-for 0.2))
    z))

(defmacro async (&rest body)
  (declare (indent 0))
  (let ((z (gensym))
        (e (gensym)))
    `(let (,e (,z 'EAGAIN))
       (cl-flet ((yield (x) (setq ,z x))
                 ;; TODO catch and re-throw instead of fail? how?
                 (fail (string &rest args) (setq ,e (cons string args))))
         ;; TODO add abort? how? is it a good idea?
         ,@body)
       (lambda () (if ,e (apply #'error ,e) ,z)))))

(await (async (yield (+ 1 41))))
(await (async (fail "hi %d" 42)))

That's it.

Now it would be good to run something in background.  I got the
following examples to work:

Assuming alet (async let) macro, which binds var when async process
finishes with the value of its output:

(alet p1 '("which" "emacs")
  (when p1
    (alet p2 `("readlink" "-f" ,p1)
      (when p2
        (message "@@@ %s" p2)))))

I can await async process:

(await
 (async
   (alet p1 '("which" "emacs")
     (when p1
       (alet p2 `("readlink" "-f" ,p1)
         (when p2
           (yield p2)))))))

or even await async process inside async process:

(await
 (async
   (alet p `("readlink" "-f" ,(await
                               (async
                                 (alet p '("which" "emacs")
                                   (when p
                                     (yield p))))))
     (when p
       (yield p)))))

This shows off async & await working with async process.  await is
annoying and not needed in this example as shown above but in some cases
it is necessary.

How does alet look like?

In the previous examples I processed the output of an async process per
line but futur.el example takes the whole output.  The only thing I need
to change is to change output chunking from line-writer to
buffer-writer and add a few convenience functions and macros:

(defmacro consume (var val &rest body)
  ;; set up async process and return immediately
  ;; body called repeatedly in background per process output event
  (declare (indent 2))
  `(funcall ,val (lambda (,var) ,@body)))

;; wrap in nicer syntax, async let, in background
(defmacro alet (var command &rest body)
  (declare (indent 2))
  (let ((cmd (gensym)))
    `(let ((,cmd ,command))
       (consume ,var (let ((b (test-buffer (format "*alet%s" ,cmd))))
                       (writer-process6
                        b
                        ,cmd
                        (lambda (writer) (buffer-writer b writer))))
         ,@body))))

;; customizeable chunking
(defun writer-process6 (buffer command chunk)
  (lambda (writer)
    (let ((w (funcall chunk writer)))
      (make-process :name (buffer-name buffer)
                    :command command
                    :buffer buffer
                    :sentinel (writer-sentinel w)
                    :filter (writer-filter w)))))

;; taken from (info "Process Filter Functions")
;; quite useful, why is this not part of emacs code?
(defun ordinary-insertion-filter (proc string)
  (when (buffer-live-p (process-buffer proc))
    (with-current-buffer (process-buffer proc)
      (let ((moving (= (point) (process-mark proc))))
        (save-excursion
          ;; Insert the text, advancing the process marker.
          (goto-char (process-mark proc))
          (insert string)
          (set-marker (process-mark proc) (point)))
        (if moving (goto-char (process-mark proc)))))))

;; like line-writer but output the whole thing
(defun buffer-writer (buffer writer)
  (let (done)
    (lambda (string)
      (unless done
        (if string
            (ordinary-insertion-filter (get-buffer-process buffer) string)
          (let ((z (with-current-buffer buffer (buffer-string))))
            (kill-buffer buffer)
            (funcall writer z))
          (funcall writer nil)
          (setq done t))))))

I think this provides nicer interface for async code than futur.el and
even comes with a working example.

Is there anything else async & await should handle but does not?



reply via email to

[Prev in Thread] Current Thread [Next in Thread]