From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: "Philip K." Newsgroups: gmane.emacs.devel Subject: Re: [ELPA] A Setup package Date: Wed, 10 Feb 2021 00:42:39 +0100 Message-ID: <87pn186c5s.fsf@posteo.net> References: <874kis6ots.fsf@posteo.net> Mime-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="32543"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) Cc: emacs-devel@gnu.org To: Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Wed Feb 10 00:43:55 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 1l9cfg-0008Hp-1M for ged-emacs-devel@m.gmane-mx.org; Wed, 10 Feb 2021 00:43:52 +0100 Original-Received: from localhost ([::1]:37744 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1l9cff-0007Si-3m for ged-emacs-devel@m.gmane-mx.org; Tue, 09 Feb 2021 18:43:51 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:43698) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1l9ced-00071Q-Dj for emacs-devel@gnu.org; Tue, 09 Feb 2021 18:42:47 -0500 Original-Received: from mout01.posteo.de ([185.67.36.65]:36541) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1l9cea-00087T-F0 for emacs-devel@gnu.org; Tue, 09 Feb 2021 18:42:47 -0500 Original-Received: from submission (posteo.de [89.146.220.130]) by mout01.posteo.de (Postfix) with ESMTPS id C475C16005C for ; Wed, 10 Feb 2021 00:42:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1612914160; bh=OfHTuwFtpPAOTy8tt6hkGWDCkGDvvvei7fUwYjBVUbQ=; h=From:To:Cc:Subject:Date:From; b=M+bgWhU3Yjt8s4uX66pC91YhWlD5OjSgc73w7DsUFnOCbz+LV7wp+TkLbJJ654X4I Kvf3I9yY4jDIHeI0ieCHP4U4ScUy03zMmeRkAnrfKXD/nBSqg9jDrHV3gT909sqP7U bc4ze5tQPraSWAVfMqo/JJodbPliR0ZArkBUzxbTfRJyIzuSMwOcKMDNki3jLRqi44 pdva1fpNQ7HjZ4RyZrkwdiIUPFuaqYglIf6CjDV1EHW6ZsJ+7XIf5oXzMt3Ovn+kC5 FyQ7R9FEmuhiTGuhUvRt2bD4Ro3CGlLW3rE2Oao8wxaryAAExccnuS+Cil+cDM1/8G 8hhrvnMiJ6vtw== Original-Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4DZzw00w8Fz6tmG; Wed, 10 Feb 2021 00:42:39 +0100 (CET) In-Reply-To: (Stefan Monnier's message of "Tue, 09 Feb 2021 16:56:08 -0500") Received-SPF: pass client-ip=185.67.36.65; envelope-from=philipk@posteo.net; helo=mout01.posteo.de X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, 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:264254 Archived-At: --=-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable Stefan Monnier writes: > 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? Yes, sorry about that. I changed providers last year, so it would be better if that could be updated. >> ;;; 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? I created one yesterday: https://git.sr.ht/~zge/setup. I'll push the improvements to this repository, and notify this thread when I am done. >> ;;; 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. Ok. >> (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. I'm not proud of it, but I haven't found a better solution yet. >> ;;;###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. Yes, that is the idea, though it wanted, the signature might just as well be (name &rest body). > 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`. I don't know how to say if it is worth the complexity or not. I like it, because it is more compact, but I might be biased. >> ;;;###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! I have only ever used Edebug, never added support for it. I'll look into this. > - `: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)))) This was a problem I was thinking about, but couldn't solve. The idea to use keywords might work though. > - I'm not sure the generality of `:wrap` is worthwhile, since it seems > it's only ever used for eval-after-load. It might be worth dropping for a first version, and replacing it with a :after-loaded keyword. >> ;; 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)))) >> ((=3D (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. Will try. >> (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. Will do, thank you for the pointer. >> (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. Can do. >> (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 had to take atom, because otherwise the example (setup shell (let ((key "C-c s")) (global (key shell)) (bind (key bury-buffer)))) wouldn't work, as "key" is a symbol. The alternative would be (or (stringp key) (symbolp key)) that might be better, because I forgot that vectors are atoms (which doesn't make sense, IMO). > 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)) I think the first alternative would be better, because it reminds me of setq, but I get your point. > 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). The idea behind using macros is that setup doesn't have to be loaded, and can be byte-compiled away. Functions would either have to be re-defined every time or aliased locally, which would require loading setup. I'm not sure how I feel about this... >> (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. Thank your for the node, that was an old definition I forgot to update. >> (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? It is probably not necessary, but I'm not sure if there were some issues with byte compilation if I didn't add it. > Stefan =2D-=20 Philip K. --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQGzBAEBCAAdFiEEbW+YL3e0aNnYosjIGB9bla4wszYFAmAjHe8ACgkQGB9bla4w szaA9wv9Eyn0CgMnmp4X1y4FhUmapQLxWUt7jImjeAW6HnOsrE+FJRdXEGX5XdLW 7mSqo73zmtEAEenyd5ZIAp7LqbUoBPiZI1lodgDEzLQZ7Y7P9GHax+u2lB/TBt6N TYrrLOCw6tZRXqVKDv+EjpJda2tIviLWQrkCuSrTwe5V81wN2n6pJMonY66y17Zk T/+qm2x5piE0FAfrRx+MFfHv9z9EmN+CtyOEuvRnVNN8UydrQ7BHLAEfY03kXmkt a3cfCWsyea1hpzDipl7Cm86wO8cN2rA/pyaapEIuscvOHFxut3+id9cJysMCbEqo QuWTxIko5x8bqiQP/8c/ndPvuR2bTcwsuVk2ImhdFVP/d+Erc2Uyci3/PevXpFAQ 04T+RkMV8Mgg2A4QSbRs09FDJmwtlRkQxrsODGQz0svvTotMwT+M31iIGvSaOb8r 4bDVzLP0flToN2yQit6d8zoT49CMZettjmgoiZC/vTg9oiLK1Pod2+gvHbrrp1Me 89VCIKvG =qkob -----END PGP SIGNATURE----- --=-=-=--