From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier Newsgroups: gmane.emacs.devel Subject: named-let Date: Fri, 08 Jan 2021 20:43:13 -0500 Message-ID: Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="3942"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sat Jan 09 02:44:27 2021 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 1ky3Io-0000tK-2W for ged-emacs-devel@m.gmane-mx.org; Sat, 09 Jan 2021 02:44:26 +0100 Original-Received: from localhost ([::1]:35852 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ky3In-0003Dc-16 for ged-emacs-devel@m.gmane-mx.org; Fri, 08 Jan 2021 20:44:25 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:52264) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ky3Hk-0002n5-9c for emacs-devel@gnu.org; Fri, 08 Jan 2021 20:43:20 -0500 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:4247) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ky3Hh-0002jT-9W for emacs-devel@gnu.org; Fri, 08 Jan 2021 20:43:19 -0500 Original-Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id E9970441360; Fri, 8 Jan 2021 20:43:15 -0500 (EST) Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 7B51244135E; Fri, 8 Jan 2021 20:43:14 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1610156594; bh=D1EmmUmOm3xMqt0877jPVWoARhFLQbQQbVlrZyBixq4=; h=From:To:Subject:Date:From; b=WuO8kV+GdZGHiY/tRhaXVZbXdDrN2DqaAVy9mMn4veNWS3zp/P83SIziK26KjoxeB ABFJBbZz7QlmD8oc42f6TGpH5ZIIZun5Wt6Cqwxnv1lfQoghg9EBj8lrbowialxg8w IUhzl2RlYNHO/fF30iiMTCx+3xzvG6dJPICsS6lgf6Cp9xnUpjeGesiolFPRq+zxNK Arx72XgTl9AMDySqBfvLqtOCZdmNnOzhw41MCqw2yZuaq1DVqPTLhnZgo+IS+ua8EC UI46vvHcAGCAK3BNcDChjPVNcT8b5/8DOV4zdXU7jXwGIQHAZC7vyQwDvU0WH+QQB9 AGrZwtTUOws5g== Original-Received: from alfajor (unknown [45.72.224.181]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 4F5531202A1; Fri, 8 Jan 2021 20:43:14 -0500 (EST) Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@iro.umontreal.ca; helo=mailscanner.iro.umontreal.ca X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=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.23 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" Xref: news.gmane.io gmane.emacs.devel:262779 Archived-At: The recent discussion around loops motivated me to look a bit further into named-let. It's actually easy to define: (defmacro named-let (name bindings &rest body) "Looping construct taken from Scheme. Like `let', bind variables in BINDINGS and then evaluate BODY, but with the twist that BODY can evaluate itself recursively by calling NAME, where the arguments passed to NAME are used as the new values of the bound variables in the recursive invocation." (declare (indent 2) (debug (symbolp (&rest (symbolp form)) body))) (let ((fargs (mapcar (lambda (b) (if (consp b) (car b) b)) bindings)) (aargs (mapcar (lambda (b) (if (consp b) (cadr b))) bindings))) ;; According to the Scheme semantics of named let, `name' is not in scope ;; while evaluating the expressions in `bindings', and for this reason, the ;; "initial" function call below needs to be outside of the `cl-labels'. `(funcall (cl-labels ((,name ,fargs . ,body)) #',name) . ,aargs))) You can then define (defun my-length (xs) (named-let loop ((xs xs) (n 0)) (if xs (loop (cdr xs) (1+ n)) n))) Now this definition of length is recursive, so without some proper tail call optimization, it'll burp on any longish list (apparently 1000 elements are sufficient). But with the recent tail-call optimization I installed into `master`, the above `my-length` now works without eating up stack space. It's still not as efficient as a hand-written `while` loop, but it's not that bad. Here's the byte-code for the above function: byte code: doc: ... args: (arg1) 0 dup 1 constant 0 2 constant nil 3:1 stack-ref 2 4 stack-ref 2 5 stack-ref 1 6 goto-if-nil 2 9 stack-ref 1 10 cdr 11 stack-set 5 13 dup 14 add1 15 stack-set 4 17 constant :recurse 18 goto 3 21:2 dup 22 stack-set 3 24 constant nil 25:3 discardN-preserve-tos 2 27 goto-if-not-nil 1 30 dup 31 stack-set 1 33 return Notice how there's really no `lambda` nor `funcall` that remains. Stefan