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: Re: [ELPA] A Setup package Date: Tue, 09 Feb 2021 16:56:08 -0500 Message-ID: References: <874kis6ots.fsf@posteo.net> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="6661"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) Cc: emacs-devel@gnu.org To: "Philip K." Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Tue Feb 09 22:58:01 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 1l9b1E-0001ag-W7 for ged-emacs-devel@m.gmane-mx.org; Tue, 09 Feb 2021 22:58:01 +0100 Original-Received: from localhost ([::1]:48882 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1l9b1D-0003vO-UJ for ged-emacs-devel@m.gmane-mx.org; Tue, 09 Feb 2021 16:57:59 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:42954) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1l9azc-0002k1-7c for emacs-devel@gnu.org; Tue, 09 Feb 2021 16:56:21 -0500 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:31443) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1l9azW-0004BN-SW for emacs-devel@gnu.org; Tue, 09 Feb 2021 16:56:19 -0500 Original-Received: from pmg2.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id C5E318090A; Tue, 9 Feb 2021 16:56:12 -0500 (EST) Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id BC57F80055; Tue, 9 Feb 2021 16:56:10 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1612907770; bh=pZfJgpe9C9DcSr5YdSZ6r9TSUmlocvwwsLYvr9BG0RE=; h=From:To:Cc:Subject:References:Date:In-Reply-To:From; b=epigTvTqvQEAbSeJYdR2FagOeY/PS8kOhx+e8CB8jeg0SJ9aZ4pO3hms2fL2KGXDn ktlyxjzZb18m12Uuj6qm8E0KP6mblu8e8S3Uq2V/v22c+lncRTfgCOqPpzqrVKuCP4 SVFst1M9x0ylztQtOdh6ShI+8WGgIBRO/6oXyQX371UJ6bYJs6wB+6wjwuBPb5vR+2 AJNBaNT7DAdpUmabwunpvzrlu6ZeNxCFURbPWH73QXcfpjkv3s4YQWLTGRt+/mUCdr W9An2mx78Y3wMXADNz1IBbpqdnzwOETC9dCol/CsmRZeumj8ZDH9ij79+w7FJDc2Qq 6FNgkRchMo/dw== Original-Received: from alfajor (unknown [216.154.41.47]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 7E94D120286; Tue, 9 Feb 2021 16:56:10 -0500 (EST) In-Reply-To: <874kis6ots.fsf@posteo.net> (Philip K.'s message of "Thu, 04 Feb 2021 12:43:11 +0100") 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:264249 Archived-At: Hi Philip, Philip K. [2021-02-04 12:43:11] wrote: > I would like to propose adding a package to ELPA: Have you already signed the copyright paperwork? I see a possible matching "Philip K..." in the list, but its email is at warpmail rather than posteo. Is that you? > ;;; setup.el --- Helpful Configuration Macro -*- lexical-binding: t -*- > > ;; Author: Philip K. > ;; Maintainer: Philip K. > ;; Version: 0.1.0 > ;; Package-Requires: ((emacs "25.1")) > ;; Keywords: lisp, local Is this file available in a Git repository somewhere? > ;;; Commentary: > > ;; The `setup' macro simplifies repetitive configuration patterns. > ;; For example, these macros: > > ;; (setup shell > ;; (let ((key "C-c s")) > ;; (global (key shell)) > ;; (bind (key bury-buffer)))) > ;; > ;; > ;; (setup (package paredit) > ;; (hide-lighter) > ;; (hook-into scheme-mode lisp-mode)) Ah, so it's to `use-package` a bit like `iterate` is to `loop`? I like that. > (eval-when-compile (require 'cl-macs)) Please use (require 'cl-lib) rather than (require 'cl-macs) because the division of labor between those files is an internal detail. > (defun setup-after-load (body) > "Wrap BODY in a `with-eval-after-load'." > ``(with-eval-after-load setup-name ,,body)) Hmm... that's a fairly subtle semantics. > ;;;###autoload > (defmacro setup (&rest body) > "Configure a feature or subsystem. > BODY may contain special forms defined by `setup-define', but > will otherwise just be evaluated as is." > (declare (indent defun)) > (let* ((name (if (and (listp (car body)) > (get (caar body) 'setup-get-name)) > (funcall (get (caar body) 'setup-get-name) > (car body)) > (car body))) Hmm.. why do you use (&rest body) instead of (name &rest body)? AFAICT this `setup-get-name` is so that you can say (setup (require shell) ...) which like (setup shell ...) except that it `require`s shell eagerly. And similarly (setup (package ivy) ...) will try and install `ivy` if it's not installed yet. Is it worth this complexity? It seems you could get similar result without this trick using a syntax like: (setup ivy (:get-package) (:require) ...) which would save you this `setup-get-name` here and the corresponding `:name` in `setup-define`. > ;;;###autoload > (defun setup-define (name fn &rest opts) > "Define `setup'-local macro NAME using function FN. > The plist OPTS may contain the key-value pairs: > > :name > Specify a function to use, for extracting the feature name of a > NAME entry, if it is the first element in a setup macro. > > :indent > Set the symbol property `lisp-indent-function' for NAME. > > :wrap > Specify a function used to wrap the results of a NAME entry. > > :sig > Give an advertised calling convention. > > :doc > A documentation string." Here are some of the problem I see: - You should add support for Edebug! - `:indent` will set the `lisp-indent-function` property on the symbol, making it apply globally rather than only within `setup`. [ The same problem will plague the `:sig` and the Edebug support, BTW. ] This is the reason why in `define-inline` I decided to prefix all the local macros with `inline-` :-( I don't have a really good solution for that (clearly, we'd like to allow `lisp-indent-function` (and Edebug's equivalent) to take some context into account, but that's not currently available. Patches welcome). But maybe to reduce the problem, you could use local macros with names that start with a `:`, as in: (setup shell (let ((key "C-c s")) (:global (key shell)) (:bind (key bury-buffer)))) - I'm not sure the generality of `:wrap` is worthwhile, since it seems it's only ever used for eval-after-load. > ;; define macro for `cl-macrolet' > (push (let ((arity (func-arity fn))) > (cl-flet ((wrap (result) > (if (plist-get opts :wrap) > (funcall (plist-get opts :wrap) result) > result))) > (cond ((eq (cdr arity) 'many) > `(,name (&rest args) ,(wrap `(apply #',fn args)))) > ((eq (cdr arity) 0) > `(,name () ,(wrap `(funcall #',fn)))) > ((= (car arity) (cdr arity)) > `(,name (&rest args) > (unless (zerop (mod (length args) ,(car arity))) > (error "Illegal arguments")) > (let ((aggr (list 'progn))) > (while args > (let ((rest (nthcdr ,(car arity) args))) > (setf (nthcdr ,(car arity) args) nil) > (push (apply #',fn args) aggr) > (setq args rest))) > ,(wrap `(nreverse aggr))))) > ((error "Illegal function"))))) > setup-macros) IIUC this expands calls that have "more args" into repeated calls, right? I suggest you make that behavior optional via some `:repeatable` keyword argument, which will be an opportunity to document that feature. That will also make it possible to accept `fn` values that allow optional arguments. > (defun setup-help () > "Generate and display a help buffer for the `setup' macro." > (interactive) > (with-help-window (help-buffer) > (princ "The `setup' macro defines the following local macros:\n\n") > (dolist (sym (mapcar #'car setup-macros)) > (let ((doc (or (get sym 'setup-documentation) > "No documentation.")) > (sig (if (get sym 'setup-signature) > (cons sym (get sym 'setup-signature)) > (list sym)))) > (princ (format "- %s\n\n%s\n\n" sig doc)))))) Take a look at the definition of `pcase` to see how you can merge the above right into the normal function's doc, so you don't need a separate command and `C-h o setup` will directly give you that info. > (setup-define 'with-mode > (lambda (mode &rest body) > `(let ((setup-mode ',mode) > (setup-map ',(intern (format "%s-map" mode))) > (setup-hook ',(intern (format "%s-hook" mode)))) > (ignore setup-mode setup-map setup-hook) > ,@body)) > :sig '(MODE &body BODY) > :doc "Change the MODE that BODY is configuring." > :indent 1) It would be nice to use this macro in the `setup` macro, to reduce the code duplication between the two. > (setup-define 'global > (lambda (bind) > (let ((key (car bind)) > (fn (cadr bind))) > `(global-set-key ,(if (atom key) `(kbd ,key) key) #',fn))) `atom`? I think you only want to use `kbd` is `key` is a string, and not if it's a vector, for example. I find the syntax (bind (key bury-buffer)) a bit confusing from a Lisp point of view. It would be nice to make it into a *function* (or at least use the syntax that corresponds to a function) so there's no confusion with a call to function `key`: (bind key 'bury-buffer) An alternative would be to go the other way and use (bind ((key bury-buffer)) BTW, I think it'd be good to allow `setup` to have not only local macros but also local functions (I think we should generally refrain from using macros when functions work about as well; it simplifies the overall system, leads to simpler semantics, and helps when debugging). > (setup-define 'hide-lighter > (lambda () > `(delq (assq setup-mode minor-mode-alist) > minor-mode-alist)) > :doc "Hide the mode-line lighter of the current mode." > :body 'after-load) I don't see `:body` handled anywhere. > (setup-define 'local-set > (lambda (assign) > (let ((var (car assign)) > (val (cadr assign))) > `(add-hook setup-hook (lambda () (setq-local ,var ,val))))) > :sig '((VAR VAL) *) > :doc "Set the value of VAR to VAL in buffers of the current mode." > :wrap #'setup-after-load) Why after-load? Stefan