From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Tomas Hlavaty Newsgroups: gmane.emacs.devel Subject: Re: continuation passing in Emacs vs. JUST-THIS-ONE Date: Sun, 26 Mar 2023 21:35:27 +0200 Message-ID: <87tty78fwg.fsf@logand.com> References: <627090382.312345.1678539189382@office.mailbox.org> <87sfe7suog.fsf@gmail.com> <1c6fedae-10b4-5d97-5036-eaa736e1b816@gmail.com> <87mt4c6xju.fsf@logand.com> <87a6001xm0.fsf@logand.com> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="40114"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Jim Porter , Karthik Chikmagalur , Thomas Koch , "emacs-devel@gnu.org" To: Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sun Mar 26 21:36:41 2023 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pgWAT-000AEJ-86 for ged-emacs-devel@m.gmane-mx.org; Sun, 26 Mar 2023 21:36:41 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pgW9b-0001LA-Dy; Sun, 26 Mar 2023 15:35:47 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pgW9Z-0001Jn-Ae for emacs-devel@gnu.org; Sun, 26 Mar 2023 15:35:45 -0400 Original-Received: from logand.com ([37.48.87.44]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pgW9X-0003nl-LI for emacs-devel@gnu.org; Sun, 26 Mar 2023 15:35:45 -0400 Original-Received: by logand.com (Postfix, from userid 1001) id 1875519E65E; Sun, 26 Mar 2023 21:35:30 +0200 (CEST) X-Mailer: emacs 28.2 (via feedmail 11-beta-1 I) In-Reply-To: <87a6001xm0.fsf@logand.com> Received-SPF: pass client-ip=37.48.87.44; envelope-from=tom@logand.com; helo=logand.com X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:304778 Archived-At: On Sat 25 Mar 2023 at 19:42, Tomas Hlavaty 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?