From mboxrd@z Thu Jan 1 00:00:00 1970 From: John Kitchin Subject: Re: lisp: scoping vars in repetitive defuns Date: Wed, 18 Sep 2019 08:48:17 -0400 Message-ID: References: Mime-Version: 1.0 Content-Type: text/plain Return-path: Received: from eggs.gnu.org ([2001:470:142:3::10]:56535) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iAZNf-0003vi-Eq for emacs-orgmode@gnu.org; Wed, 18 Sep 2019 08:48:25 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iAZNd-0000po-GJ for emacs-orgmode@gnu.org; Wed, 18 Sep 2019 08:48:23 -0400 Received: from mail-qk1-x731.google.com ([2607:f8b0:4864:20::731]:37663) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1iAZNd-0000pQ-9T for emacs-orgmode@gnu.org; Wed, 18 Sep 2019 08:48:21 -0400 Received: by mail-qk1-x731.google.com with SMTP id u184so7880763qkd.4 for ; Wed, 18 Sep 2019 05:48:20 -0700 (PDT) In-reply-to: List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: "Emacs-orgmode" To: Matt Price Cc: Org Mode You don't really need a macro for this I think. I see it leads to pretty clean looking code, but as you noted at the expense of edebuggable functions. I don't think you need the lexical-let in your macro though. With empty arguments I am not sure it does anything. Here are some other approaches to do what you want. I wasn't able to test these, but I think they are close to correct and would provide edebuggable functions for you. The plist is more flexible and future proof, you can add anything you want to it, and it won't mess up existing functions. The first approach you can add what you want to the end of the list, but you can't change the order without (probably) messing existing functions up. #+BEGIN_SRC emacs-lisp (require 'f) (defun dh-variables () "Return a list of variables for the problem at point." (let* ((gh (org-entry-get (point) "GITHUB")) (base (org-entry-get (point) "ORG_LMS_ASSIGNMENT_DIRECTORY")) (findFiles (list (format "Reflection/%s.md" gh) (format "students/%s.md" gh))) (browseFiles '("index.html")) (testOutput "TestResults/testresults.html") (testCommand "MARKING=instructor npm test")) (list gh base findFiles browseFiles testOutput testCommand))) ;; Here we assign all the values from the function above to variable names. (defun dh-view () "Open viewable files in browser" (destructuring-bind (gh base findFiles browseFiles testOutput testCommand) (dh-variables) (loop for f in browseFiles do (browse-url-of-file (f-join base f))))) ;; alternative with a plist (defun dh-variables () "Return a plist of variables for the problem at point." (let* ((gh (org-entry-get (point) "GITHUB")) (base (org-entry-get (point) "ORG_LMS_ASSIGNMENT_DIRECTORY")) (findFiles (list (format "Reflection/%s.md" gh) (format "students/%s.md" gh))) (browseFiles '("index.html")) (testOutput "TestResults/testresults.html") (testCommand "MARKING=instructor npm test")) (list :gh gh :base base :findFiles findFiles :browseFiles browseFiles :testOutput testOutput :testCommand testCommand))) (defun dh-view () "Open viewable files in browser" (loop for f in (plist-get (dh-variables) :browseFiles) do (browse-url-of-file (f-join base f)))) ;; Or with the plist as an argument (defun dh-view (var-plist) "Open viewable files in browser" (loop for f in (plist-get var-plist :browseFiles) do (browse-url-of-file (f-join base f)))) ;; called like this (dh-view (dh-variables)) #+END_SRC Matt Price writes: > On Tue, Sep 17, 2019 at 8:46 AM John Kitchin > wrote: > >> I don't totally understand what you are trying to do here. >> > > I think the explanation was a little unclear! > > >> If this were Python, it sounds like you want some kind of class that >> stores a variable and reuses it several different functions? Something kind >> of similar to that in elisp is a closure, which is like what you described. >> For example, here, we define a variable a, and then define two functions >> that use it persistently. >> >> >> I think you can wrap this in a macro to make new functions, e.g. >> >> #+BEGIN_SRC emacs-lisp >> (defmacro f-maker (a) >> `(lexical-let ((a ,a)) >> (defun f1 (x) >> (* a x)) >> >> (defun f2 (x) >> (+ a x)))) >> >> (f-maker 3) >> >> (list (f1 2) (f2 2)) >> #+END_SRC >> >> #+RESULTS: >> | 6 | 5 | >> >> This is basically what I want, except it turned out to be easier to just > wrap the body forms in a let *within*the function. THis is what I came up > with: > > #+BEGIN_SRC emacs-lisp > > (defmacro dh-factory (name body &optional docstring) > "A helper macro that sets up the environment to simplify defining multiple > functions with the same environment variables. NAME will bcome the functin > name, BODY is a list containing the lisp forms specific to the function, > and DOCSTRING is an optional ... docstring. NAME wil lbe wrapped in a > `let` statement setting all the remelvant variables." > `(lexical-let (()) > (defun ,name ,() > ,docstring > (interactive) > (let* ((gh (org-entry-get (point) "GITHUB")) > (base (org-entry-get (point) "ORG_LMS_ASSIGNMENT_DIRECTORY")) > (findFiles `( ,(concat "Reflection/" gh ".md") ,(concat > "students/" gh ".json"))) > (browseFiles `( "index.html" )) > (testOutput "TestResults/testresults.html") > (testCommand "MARKING=instructor npm test")) > ,@body)))) > > (dh-factory dh-find-files > ((dolist (f findFiles) > (message "%s" (concat base "/" f)) > (if (file-exists-p (concat base "/" f)) > (find-file-other-window (concat base "/" f) ) > (message "File %s does not exist, not opening." f))) ) > "Open gradable files in Emacs windows") > > (dh-factory dh-view > ((loop for f in browseFiles > do > (browse-url-of-file (concat base "/" f )))) > "open viewable files in browser") > > ;; this one should really pass a variable to allow different branches! oh > well. > (dh-factory dh-status > ((magit-status base) > (let ((currentBranch (shell-command-to-string > (format "cd %s && git rev-parse > --abbrev-ref HEAD" base) )) > (currentCommit (shell-command-to-string > (format "cd %s && git rev-parse HEAD" > base)))) > (magit-stash-worktree > (format "Stashed random files from %s after commit %s." > currentBranch currentCommit))) > (magit-checkout (concat gh "-master"))) > "Open a magit-status buffer and check out this student's branch > in the repo") > > > (dh-factory dh-tests > ((let ((output)) > (with-temp-buffer > (message (concat "cd " base " ; npm test")) > (setq output (shell-command-to-string (concat "cd " base " ; > npm test")))) > (message "WELL, sorta made it through: %s" output) > (browse-url-of-file (concat base testOutput)))) > "Run tests directly from macs and view output") > > > #+END_SRC > > I quite like it, though it's a bit unwieldy not to have the the source code > available for edebug etc. It feels a little like having a namespace; I'm > not afraid of polluting the global namespace, but I can use these symbol > names throughout the code I'm processing via the factory. > > I'm not sure it's *all* that much more efficient though... > >> >> There is also a class system you can probably use like this called eieio, >> Here is one approach to doing this. >> >> #+BEGIN_SRC emacs-lisp >> (require 'eieio) >> >> (defclass Grade () >> ((:a :initarg :a))) >> >> (cl-defmethod gf1 ((g Grade) x) >> (* (oref g :a) x)) >> >> (cl-defmethod gf2 ((g Grade) x) >> (+ (oref g :a) x)) >> >> (let ((G (Grade :a 3))) >> (list (gf1 G 2) (gf2 G 2))) >> #+END_SRC >> >> #+RESULTS: >> | 6 | 5 | >> >> I would love to learn to use eieio but it feels like a bit of a jump. > >> >> Finally, maybe the simplest thing to do is pass this information in as an >> argument, e.g. a plist or alist, or get them from a function that you only >> change in one place? >> > > That might have been more sensible but this was pretty fun! > > Thanks everyone for the help! > > > >> On Tue, Sep 17, 2019 at 7:31 AM Matt Price wrote: >> >>> I have a number of convenience functions define to help me with grading >>> assignments. As I go through the semester, i update all of these functions >>> modestly so that they'rehelpful for grading the current assignment. >>> >>> I big chunk of these simple functions is taken up just declaring >>> variables with (let (())) forms. Each function uses some ofhte same >>> variables, e.g: >>> >>> (defun dh-find-files () >>> (interactive) >>> (let* ((base (org-entry-get (point) "ORG_LMS_ASSIGNMENT_DIRECTORY")) >>> (gh (org-entry-get (point) "GITHUB")) >>> (f2o `( ,(concat "Reflection/" gh ".md") ,(concat "students/" gh >>> ".json")))) ;;;; "01/index.html" "02/index.html" "03/style.css" >>> "04/style.css" >>> (message "%s" f2o) >>> ;; make more flexible for resubmits >>> (shell-command (concat "cd " base " && git checkout " gh "-master")) >>> (dolist (x f2o) >>> (if (file-exists-p (concat base "/" x)) >>> (find-file-other-window (concat base "/" x) ) >>> (message "File %s does not exist, not opening." x))))) >>> >>> (defun dh-tests () >>> (interactive) >>> (let* ((base (org-entry-get (point) "ORG_LMS_ASSIGNMENT_DIRECTORY" )) >>> (gh (org-entry-get (point) "GITHUB"))) >>> (with-temp-buffer (shell-command (concat "cd " base " && npm test") >>> t)) ;; the "t" lets us suppress buffer >>> (browse-url-of-file (concat base "/TestResults/testresults.html")) >>> ;; (dh-mocha-run) >>> >>> )) >>> >>> ---------- >>> >>> This semester I changed some elements of my workflow and I had to update >>> all the (org-entry-get) calls to new values. It makes me think the code is >>> less maintainable than it could be. I would like to do something like this: >>> >>> (lexical-let ((base `(org-entry-get (point) >>> "ORG_LMS_ASSIGNMENT_DIRECTORY") >>> (gh `(org-entry-get (point) "GITHUB")) ) >>> (defun dh-find-files () >>> (with-temp-buffer (shell-command (concat "cd " base " && npm test") t)) >>> ;; the "t" lets us suppress buffer >>> (browse-url-of-file (concat base "/TestResults/testresults.html"))))) >>> >>> >>> Obviously it doesn't work this way. But is there any way to set macros >>> like this to be expanded later inside a function definition? I feel certain >>> there must be... >>> >>> Thanks, >>> >>> Matt >>> >> -- Professor John Kitchin Doherty Hall A207F Department of Chemical Engineering Carnegie Mellon University Pittsburgh, PA 15213 412-268-7803 @johnkitchin http://kitchingroup.cheme.cmu.edu