From mboxrd@z Thu Jan 1 00:00:00 1970 From: Matt Price Subject: Re: lisp: scoping vars in repetitive defuns Date: Tue, 17 Sep 2019 21:12:22 -0400 Message-ID: References: Mime-Version: 1.0 Content-Type: multipart/alternative; boundary="0000000000002fdee70592c98818" Return-path: Received: from eggs.gnu.org ([2001:470:142:3::10]:33996) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iAOWM-0001M5-8T for emacs-orgmode@gnu.org; Tue, 17 Sep 2019 21:12:40 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iAOWJ-0000kO-TV for emacs-orgmode@gnu.org; Tue, 17 Sep 2019 21:12:38 -0400 Received: from mail-pf1-x42d.google.com ([2607:f8b0:4864:20::42d]:33862) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1iAOWJ-0000k6-Jw for emacs-orgmode@gnu.org; Tue, 17 Sep 2019 21:12:35 -0400 Received: by mail-pf1-x42d.google.com with SMTP id b128so3253275pfa.1 for ; Tue, 17 Sep 2019 18:12:35 -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: John Kitchin Cc: Org Mode --0000000000002fdee70592c98818 Content-Type: text/plain; charset="UTF-8" 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 >> > --0000000000002fdee70592c98818 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


=
On Tue, Sep 17, 2019 at 8:46 AM John = Kitchin <jkitchin@andrew.cmu.= edu> wrote:
I don't totally understand what you are trying to d= o here.

I think the explanation was = a little unclear!
=C2=A0
If this were Python, it sounds like you= want some kind of class that stores a variable and reuses it several diffe= rent functions? Something kind of similar to that in elisp is a closure, wh= ich 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 functi= ons, e.g.=C2=A0

#+BEGIN_SRC emacs-lisp
(defmacr= o f-maker (a)
=C2=A0 `(lexical-let ((a ,a))
=C2=A0 =C2=A0 =C2=A0(defu= n f1 (x)
=C2=A0 =C2=A0 =C2=A0 =C2=A0(* a x))

=C2=A0 =C2=A0 =C2=A0= (defun f2 (x)
=C2=A0 =C2=A0 =C2=A0 =C2=A0(+ 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 *w= ithin*the function.=C2=A0 THis is what I came up with:

=
#+BEGIN_SRC emacs-lisp

(defmacro dh-factory (name body &opt= ional docstring)
"A helper macro that sets up the environment to si= mplify defining multiple functions with the same environment variables. NAM= E will bcome the functin name, BODY is a list containing the lisp forms spe= cific to the function, and DOCSTRING is an optional ... docstring.=C2=A0 NA= ME wil lbe wrapped in a `let` statement setting all the remelvant variables= ."
=C2=A0 `(lexical-let (())
=C2=A0 =C2=A0 =C2=A0(defun ,name ,(= )
=C2=A0 =C2=A0 =C2=A0 =C2=A0,docstring
=C2=A0 =C2=A0 =C2=A0 =C2=A0(i= nteractive)
=C2=A0 =C2=A0 =C2=A0 =C2=A0(let* ((gh (org-entry-get (point)= "GITHUB"))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(b= ase (org-entry-get (point) "ORG_LMS_ASSIGNMENT_DIRECTORY"))
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(findFiles `( ,(concat &quo= t;Reflection/" gh ".md") ,(concat "students/" gh &= quot;.json")))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(bro= wseFiles `( "index.html" ))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0(testOutput "TestResults/testresults.html")
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(testCommand "MARKING=3Di= nstructor npm test"))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0,@body))))<= br>
(dh-factory dh-find-files
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 ((dolist (f findFiles)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0(message "%s" (concat base "/" f))
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(if (file-exists-p (concat = base "/" f))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0(find-file-other-window (concat base "/" f) )=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(message = "File %s does not exist, not opening." f))) =C2=A0)
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "Open gradable files in Emacs windo= ws")

(dh-factory dh-view
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 ((loop for f in browseFiles
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 do
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (browse-url-of-file (concat base "= /" f ))))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "open view= able files in browser")

;; this one should really pass a variab= le to allow different branches! oh well. =C2=A0
(dh-factory dh-status=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ((magit-status base)
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(let ((currentBranch (shell-comman= d-to-string
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(format &= quot;cd %s && git rev-parse --abbrev-ref HEAD" base) ))
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(currentC= ommit (shell-command-to-string
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0(format "cd %s && git rev-parse HEAD" base))))<= br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(magit-stash-work= tree
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 (format "Stashed random files from %s after commit %s." curre= ntBranch currentCommit)))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0(magit-checkout (concat gh "-master")))
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 "Open a magit-status buffer and check out thi= s student's branch in the repo")


(dh-factory dh-tests=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ((let ((output))
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (with-temp-buffer
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(message (concat "cd " base &qu= ot; ; npm test"))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0(setq output (shell-command-to-string (concat "cd " base &q= uot; ; npm test"))))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (mes= sage "WELL, sorta made it through: %s" output)
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(browse-url-of-file (concat base testOutp= ut))))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "Run tests directl= y from macs and view output")


#+END_SRC

I= quite like it, though it's a bit unwieldy not to have the the source c= ode available for edebug etc. It feels a little like having a namespace; I&= #39;m not afraid of=C2=A0 polluting the global namespace, but I can use the= se symbol names throughout the code I'm processing via the factory.=C2= =A0

I'm not sure it's *all* that much= more efficient though...

There is al= so 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 ()
=C2=A0 ((:a :initarg :a))= )

(cl-defmethod gf1 ((g Grade) x)
=C2=A0 (* (oref g :a) x))
(cl-defmethod gf2 ((g Grade) x)
=C2=A0 (+ (oref g :a) x))

(let = ((G (Grade :a 3)))
=C2=A0 (list (gf1 G 2) (gf2 G 2)))
#+END_SRC
#+RESULTS:
| 6 | 5 |

I would lov= e to learn to use eieio but it feels like a bit of a jump. =C2=A0

Finally, maybe the simplest thing to do is pass this information in a= s an argument, e.g. a plist or alist, or get them from a function that you = only change in one place?=C2=A0

That might have been more sensible but= this was pretty fun!

Thanks everyone for the help= !=C2=A0



On Tue, Sep 17, 2019 at 7:31 AM Matt Price <moptop99@gmail.com> wro= te:
I have a number of convenience functions define to help me with g= rading assignments. As I go through the semester, i update all of these fun= ctions modestly so that they'rehelpful for grading the current assignme= nt.=C2=A0

I big chunk of these simple functio= ns is taken up just declaring variables with (let (())) forms.=C2=A0 Each f= unction uses some ofhte same variables, e.g:

(defu= n dh-find-files ()
=C2=A0 (interactive)
=C2=A0 (let* ((base (org-entr= y-get (point) "ORG_LMS_ASSIGNMENT_DIRECTORY"))
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0(gh (org-entry-get (point) "GITHUB"))
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 (f2o `( ,(concat "Reflection/" gh "= .md") ,(concat "students/" gh ".json")))) ;;;; &qu= ot;01/index.html" "02/index.html" "03/style.css" &= quot;04/style.css"
=C2=A0 =C2=A0 (message "%s" f2o)
= =C2=A0 =C2=A0 ;; make more flexible for resubmits
=C2=A0 =C2=A0 (shell-c= ommand (concat "cd " base " && git checkout " g= h "-master"))
=C2=A0 =C2=A0 (dolist (x f2o)
=C2=A0 =C2=A0 = =C2=A0 (if (file-exists-p (concat base "/" x))
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 (find-file-other-window (concat base "/" x) = )
=C2=A0 =C2=A0 =C2=A0 =C2=A0 (message "File %s does not exist, not= opening." x)))))

(defun dh-tests ()
= =C2=A0 (interactive)
=C2=A0 (let* ((base (org-entry-get (point) "OR= G_LMS_ASSIGNMENT_DIRECTORY" ))
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(= gh (org-entry-get (point) "GITHUB")))
=C2=A0 =C2=A0 (with-temp= -buffer (shell-command (concat "cd " base " && npm t= est") t)) ;; the "t" lets us suppress buffer
=C2=A0 =C2= =A0 (browse-url-of-file (concat base "/TestResults/testresults.html&qu= ot;))
=C2=A0 =C2=A0 ;; (dh-mocha-run)
=C2=A0 =C2=A0
=C2=A0 =C2=A0= ))

----------

This semester I changed some= elements of my workflow and I had to update all the (org-entry-get) calls = to new values.=C2=A0 It makes me think the code is less maintainable than i= t could be.=C2=A0 I would like to do something like this:

(lexical-let ((base `(org-entry-get (point) "ORG_LMS_ASSIGNMEN= T_DIRECTORY")
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 (gh `(org-e= ntry-get (point) "GITHUB")) )
=C2=A0=C2=A0=C2=A0 (defun= dh-find-files ()
(with-temp-buffer (shell-command (concat "= cd " base " && npm test") t)) ;; the "t" l= ets us suppress buffer
=C2=A0 =C2=A0 (browse-url-of-file (concat base &q= uot;/TestResults/testresults.html")))))


<= /div>
Obviously it doesn't work this way. But is there any way to s= et macros like this to be expanded later inside a function definition? I fe= el certain there must be...

Thanks,

=
Matt
--0000000000002fdee70592c98818--