From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Andrew Hyatt Newsgroups: gmane.emacs.devel Subject: Re: pcase defuns Date: Sat, 26 Mar 2022 13:41:26 -0400 Message-ID: References: Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="27740"; mail-complaints-to="usenet@ciao.gmane.io" To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sat Mar 26 18:48:39 2022 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 1nYAWl-00076u-Jq for ged-emacs-devel@m.gmane-mx.org; Sat, 26 Mar 2022 18:48:39 +0100 Original-Received: from localhost ([::1]:45062 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1nYAWk-0006cT-9V for ged-emacs-devel@m.gmane-mx.org; Sat, 26 Mar 2022 13:48:38 -0400 Original-Received: from eggs.gnu.org ([209.51.188.92]:42524) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1nYAPv-00048C-FF for emacs-devel@gnu.org; Sat, 26 Mar 2022 13:41:35 -0400 Original-Received: from [2607:f8b0:4864:20::735] (port=43680 helo=mail-qk1-x735.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1nYAPt-0008H4-NX for emacs-devel@gnu.org; Sat, 26 Mar 2022 13:41:35 -0400 Original-Received: by mail-qk1-x735.google.com with SMTP id p25so8335691qkj.10 for ; Sat, 26 Mar 2022 10:41:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:subject:in-reply-to:references:date:message-id:mime-version; bh=/pMyo+9X3DWdPnvRVot2/Tra81CdD6auwC+WICBwYpY=; b=qTk43PP7ZV2i8bREfYJDAuAcSsK/i4AWnyh5EViX4a09O452SCJLmrD5QG0s4yFZ6+ dc8+rBNuTQ1hFcsZco0LnKgvegMSpJ64vtpMWuLJSl+jRqeiDCX+bObF2bpww7HaLsUx h5+Mi2K4sSfIG8h3SlmRLerDBjqASr6jE+Jqme8lzkDeVdrFHq+kRG6nWJKQaUthdOLB nfDx46OTy1ySuDnZW2JX9VAyM9BCMgte55QfYjLCfyYEEc8KGSGpa4bAYa0Rr+EmhidJ lmXTQwlSxEv2A3zqLqByzFKOOqfEpalx2hbyjc1rflHF8S75rHPM+QqYuL9de9Mb7oN5 BuXQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:subject:in-reply-to:references:date :message-id:mime-version; bh=/pMyo+9X3DWdPnvRVot2/Tra81CdD6auwC+WICBwYpY=; b=j5aKDTlMRe0zboAAKyuqXgRR+iqS0foIAT+R/filkTw3sUaGNsEKhd5RXaee2Crtod Uxd/HgesZZi8J9iyLsqPzdkYx1TT/hDLY6HQLEfjyOKfbFz9LBsI9RGWl5TeOlG8qKiB WXUjn4LjMtD1c7kPEDgnkd1WyUv5tCaeFYap/T8Uof+woZKWr0oqTrM43nvmAgvR9A0c GQX3LrylqA0OPdNdQu6yOxuS81GALK+vl0+nZxxgWsUaKZWX4Ss87GX/r6AbAijE+GdD zYNnwUOjWWs/5agSGI5ofsgEnsUJ2tXH3CQ/Xv+in6gitVLI3s3SHUdIrdNO3wZyE20r eGog== X-Gm-Message-State: AOAM5339mbKe0gsnQWLOeegZzUTwkxvunhJKsgpinjWQlW7s8Tm2iX49 YPNnzz7hqTYi/QaWAgQY4pqYfd82vpU= X-Google-Smtp-Source: ABdhPJwKeFEuryLXY7OJ/zvc0rDr5c000aDx+2775+B2AXfp/pobXE3fX7a06sCKcP98LnQ4XHGVQw== X-Received: by 2002:a05:620a:199d:b0:67d:14e0:1267 with SMTP id bm29-20020a05620a199d00b0067d14e01267mr10568375qkb.521.1648316487830; Sat, 26 Mar 2022 10:41:27 -0700 (PDT) Original-Received: from andrews-mbp.lan (pool-173-56-75-166.nycmny.fios.verizon.net. [173.56.75.166]) by smtp.gmail.com with ESMTPSA id x19-20020a05620a14b300b0067e09a47e39sm4982981qkj.34.2022.03.26.10.41.26 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 26 Mar 2022 10:41:26 -0700 (PDT) In-Reply-To: X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::735 (failed) Received-SPF: pass client-ip=2607:f8b0:4864:20::735; envelope-from=ahyatt@gmail.com; helo=mail-qk1-x735.google.com X-Spam_score_int: -6 X-Spam_score: -0.7 X-Spam_bar: / X-Spam_report: (-0.7 / 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, FREEMAIL_FROM=0.001, PDS_HP_HELO_NORDNS=0.659, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no 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" Xref: news.gmane.io gmane.emacs.devel:287487 Archived-At: --=-=-= Content-Type: text/plain; format=flowed Based on the results of the ensuing discussion, I decided to refine my macro to just be one coherent defun. I experimented with a few different ways to do this, but settled on something that seemed relatively clear and minimal, with pattern matchers that seemed like a better fit for arglists than what exists in pcase already (but it's all pcase under the hood). To give a flavor of what it looks like in practice, here's a few pattern defuns from the test: (defun-pattern fibonacci "Compute the fibonacci sequence." ((0) 0) ((1) 1) ((n) (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) (defun-pattern crit "D&D 5e critical hit computation, taking in the results of a d20 and a list of attributes currently in effect. Returns whether the results are a crit or not." ((20 _) t) ((19 (pred (member 'improved-crit))) t)) I'm attaching the package, which works, although there's some non-ideal parts of the implementation. If people think it is valuable, I can add this to GNU ELPA. --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=defun-pattern.el Content-Transfer-Encoding: quoted-printable ;;; defun-pattern.el --- A way to define methods that have multiple pattern= -style arglists and bodies. ;;; Commentary: ;; This code provides a way to have a defun that, instead of having a single ;; arglist, has many, which are pattern-matched against. Each arglist then = has ;; its own corresponding body. ;; ;; The implementation is based on pcase, and works by transforming the argl= ists ;; and bodies into a corresponding backquoted pcase. ;; ;; For usage, see `defun-pattern' documentation. ;; ;; CAUTION: as of now, there is no ability to edebug the defun. ;;; Code: (require 'pcase) (defun defun-pattern-unquote (elem) "If ELEM is a inserted element, return uninserted version of ELEM. Otherwise, if it isn't inserted, return ELEM." (pcase elem ((and (pred listp) ls (guard (=3D 2 (length ls))) (guard (equal 'quote (car ls)))) (cadr ls)) (e e))) (defconst defun-pattern-known-funcs '(rx or and let guard app pred cl-type seq) "All functions that need to be unquoted in a backquoted pcase.") (defun defun-pattern-needs-comma (elem) "Return t if ELEM is a classic pcase pattern." ;; Lists include quoted elements and functions. (pcase elem ((and (pred listp) ls) (member (car ls) defun-pattern-known-funcs)) ((pred symbolp) t))) (defun defun-pattern-correct-quote (pattern) "Correct quoting for our backquote style. Argument PATTERN is a pattern found in an `defun-pattern' arglist." (pcase pattern ((pred defun-pattern-needs-comma) (list '\, pattern)) ((pred null) nil) ((pred listp) (mapcar #'defun-pattern-correct-quote (defun-pattern-unquote pattern))) (_ pattern))) (defun defun-pattern-to-pcase (pattern) "Transform a `defun-pattern' matcher to a pcase matcher. Argument PATTERN is an arglist that needs to be transformed." (list '\` (defun-pattern-correct-quote pattern))) (def-edebug-elem-spec 'defun-pattern-patterns '((sexp def-body))) (def-edebug-spec defun-pattern (&define name [&optional stringp] &rest defun-pattern-patterns)) ;;;###autoload (defmacro defun-pattern (func &rest sub-defs) "Define a function with multiple arglists that are matched. Each arglist is paired with a body function. All arglists are based of the backquoted pcase form. If nothing matches, `nil' is returned. The patterns are basically the same as backquoted pcase, except for quoting differences to make it more similar to normal function arglists. The following differences exist from backquoted pcase: 1. The list is not backquoted. 2. Nothing needs to be prefixed with a comma. 3. Symbols, when they don't represent variables, but rather constants to match against, need to be quoted. Example: (defun-pattern fibonacci \"Compute the fibonacci sequence.\" ((0) 0) ((1) 1) ((n) (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) Further examples can be found in defun-pattern-test.el. " (declare (doc-string 2) (indent 2)) `(defun ,func (&rest args) (pcase args ,@(mapcar (lambda (d) (list (defun-pattern-to-pcase (car d)) (if (> (length (cdr d)) 1) (append '(progn) (cdr d)) (cadr d)))) ;; Declaring the doc-string doesn't seem to actually remov= e the ;; doc-string from the arg list, so we have to filter it o= ut ;; here. (if (stringp (car sub-defs)) (cdr sub-defs) sub-defs))))) (provide 'defun-pattern) ;;; defun-pattern.el ends here --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=defun-pattern-test.el Content-Transfer-Encoding: quoted-printable ;; -*- lexical-binding: t; -*- (require 'ert) (ert-deftest defun-pattern-test-constants-and-variables () (defun-pattern fibonacci "Compute the fibonacci sequence." ((0) 0) ((1) 1) ((n) (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) (should (=3D (fibonacci 0) 0)) (should (=3D (fibonacci 1) 1)) (should (=3D (fibonacci 2) 1)) (should (=3D (fibonacci 3) 2)) (should (=3D (fibonacci 8) 21))) (ert-deftest defun-pattern-test-mixed () (defun-pattern crit "D&D 5e critical hit computation, taking in the results of a d20 and a list of attributes currently in effect." ((20 _) t) ((19 (pred (member 'improved-crit))) t)) (should-not (crit 1 '(improved-crit))) (should-not (crit 19 '(other-attribute))) (should (crit 19 '(improved-crit))) (should (crit 20 nil))) (ert-deftest defun-pattern-test-repeated () (defun-pattern repeatedp "Test for repeated pattern, returns nil, 'once, or 'twice, or 'split." ((a a) 'once) ((a a a) 'twice) ((a b a) 'split)) (should-not (repeatedp 3)) (should-not (repeatedp 3 1 1)) (should-not (repeatedp 3 3 1)) (should (eq 'once (repeatedp 3 3))) (should (eq 'once (repeatedp 'z 'z))) (should (eq 'twice (repeatedp 3 3 3))) (should (eq 'split (repeatedp 3 'z 3)))) (ert-deftest defun-pattern-test-destruct () (defun-pattern middle-eq-plus-one "Test for destructuring patterns." (((_ a _) b) (=3D (+ 1 a) b))) (should-not (middle-eq-plus-one '(3) 3)) (should-not (middle-eq-plus-one '(z) 'z)) (should-not (middle-eq-plus-one '(1 3 9) 3)) (should (middle-eq-plus-one '(1 3 9) 4))) --=-=-= Content-Type: text/plain; format=flowed On Sat, Dec 18, 2021 at 11:53 PM Andrew Hyatt wrote: > Hi all, > > As a part of a personal project, I wrote a way to define functions > in an equivalent way to pcases. For example: > > (pcase-defun mytest (a b _) > "Match on 'a 'b with the third argument a wildcard" > "a b match") > > (pcase-defun mytest (c ,var _) > "Match on 'c binding VAR, with the third argument a wildcard" > (format "c %s match" var) ) > > (mytest 'a 'b 'c) -> "a b match" > (mytest 'c 100 'c) -> "c 100 match" > > This is all accomplished by a few small but tricky macros and a > hashtable that holds all the rules. > > I find this method of programming quite useful for certain kinds > of problems, and pcase is close to what I want, but using pcase > forced me to encapsulate all of my pattern matching logic in one > function. Being able to have it spread out in pattern matching > functions seems much nicer to me. > > If this is of interest, I can either add this to the pcase module > itself, create a package in gnu elpa, or (if there's some reason > that it shouldn't be done) just keep it in a personal repository. > > I'll wait for some guidance before creating a final version, with > tests, wherever it should go. --=-=-=--