emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* lisp: scoping vars in repetitive defuns
@ 2019-09-17 11:30 Matt Price
  2019-09-17 12:46 ` John Kitchin
  2019-09-17 13:42 ` Nick Dokos
  0 siblings, 2 replies; 11+ messages in thread
From: Matt Price @ 2019-09-17 11:30 UTC (permalink / raw)
  To: Org Mode

[-- Attachment #1: Type: text/plain, Size: 2132 bytes --]

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

[-- Attachment #2: Type: text/html, Size: 2883 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-17 11:30 lisp: scoping vars in repetitive defuns Matt Price
@ 2019-09-17 12:46 ` John Kitchin
  2019-09-18  1:12   ` Matt Price
  2019-09-17 13:42 ` Nick Dokos
  1 sibling, 1 reply; 11+ messages in thread
From: John Kitchin @ 2019-09-17 12:46 UTC (permalink / raw)
  To: Matt Price; +Cc: Org Mode

[-- Attachment #1: Type: text/plain, Size: 3914 bytes --]

I don't totally understand what you are trying to do here. 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.

#+BEGIN_SRC emacs-lisp
(lexical-let ((a 5))
  (defun f1 (x)
    (* a x))

  (defun f2 (x)
    (+ a x)))


(list (f1 2) (f2 2))
#+END_SRC

#+RESULTS:
| 10 | 7 |


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 |


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 |


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?

John

-----------------------------------
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



On Tue, Sep 17, 2019 at 7:31 AM Matt Price <moptop99@gmail.com> 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
>

[-- Attachment #2: Type: text/html, Size: 5518 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-17 11:30 lisp: scoping vars in repetitive defuns Matt Price
  2019-09-17 12:46 ` John Kitchin
@ 2019-09-17 13:42 ` Nick Dokos
  1 sibling, 0 replies; 11+ messages in thread
From: Nick Dokos @ 2019-09-17 13:42 UTC (permalink / raw)
  To: emacs-orgmode

Matt Price <moptop99@gmail.com> writes:

> 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...

Are you overthinking this perhaps? Wouldn't variables work? Like e.g.:

(defvar org-lms-assignment-dir-prop "ORG_LMS_ASSIGNMENT_DIRECTORY")

(defun dh-find-files()
  ...
  (let ((base (org-entry-get (point) org-lms-assignment-dir)
      ...))))

and then you only have one place to change it?

It's quite possible of course that I'm misunderstanding what you are looking for.

>
> Thanks,
>
> Matt
>

-- 
Nick

"There are only two hard problems in computer science: cache
invalidation, naming things, and off-by-one errors." -Martin Fowler

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-17 12:46 ` John Kitchin
@ 2019-09-18  1:12   ` Matt Price
  2019-09-18 12:48     ` John Kitchin
  0 siblings, 1 reply; 11+ messages in thread
From: Matt Price @ 2019-09-18  1:12 UTC (permalink / raw)
  To: John Kitchin; +Cc: Org Mode

[-- Attachment #1: Type: text/plain, Size: 7084 bytes --]

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 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 <moptop99@gmail.com> 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
>>
>

[-- Attachment #2: Type: text/html, Size: 10396 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-18  1:12   ` Matt Price
@ 2019-09-18 12:48     ` John Kitchin
  2019-09-18 20:52       ` Matt Price
  0 siblings, 1 reply; 11+ messages in thread
From: John Kitchin @ 2019-09-18 12:48 UTC (permalink / raw)
  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 <moptop99@gmail.com> writes:

> 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 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 <moptop99@gmail.com> 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

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-18 12:48     ` John Kitchin
@ 2019-09-18 20:52       ` Matt Price
  2019-09-18 21:26         ` Joost Kremers
  2019-09-18 21:42         ` Adam Porter
  0 siblings, 2 replies; 11+ messages in thread
From: Matt Price @ 2019-09-18 20:52 UTC (permalink / raw)
  To: John Kitchin; +Cc: Org Mode

[-- Attachment #1: Type: text/plain, Size: 11538 bytes --]

This is fun, thanks John. I really like the plist version put would also
like to loop through the variables in a let statement somehow.

I think what I'm missing is the equivalent of a javascript implicit
destructuring construct:

let { } = object;

which will define new variables prop1, prop2... forever enumerable property
of the object.  Is thre away to do that kind of destructuring bind -- which
binds *everything* in the plist, without knowing the symbol names in
advance? that would be really great.



On Wed, Sep 18, 2019 at 8:48 AM John Kitchin <jkitchin@andrew.cmu.edu>
wrote:

> 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 <moptop99@gmail.com> writes:
>
> > 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 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 <moptop99@gmail.com> 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
>

[-- Attachment #2: Type: text/html, Size: 15694 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-18 20:52       ` Matt Price
@ 2019-09-18 21:26         ` Joost Kremers
  2019-09-18 21:44           ` Adam Porter
  2019-09-18 21:42         ` Adam Porter
  1 sibling, 1 reply; 11+ messages in thread
From: Joost Kremers @ 2019-09-18 21:26 UTC (permalink / raw)
  To: emacs-orgmode; +Cc: John Kitchin


On Wed, Sep 18 2019, Matt Price wrote:
> Is thre away to do that kind of destructuring bind -- which
> binds *everything* in the plist, without knowing the symbol 
> names in
> advance? that would be really great.

let-alist perhaps?


-- 
Joost Kremers
Life has its moments

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-18 20:52       ` Matt Price
  2019-09-18 21:26         ` Joost Kremers
@ 2019-09-18 21:42         ` Adam Porter
  1 sibling, 0 replies; 11+ messages in thread
From: Adam Porter @ 2019-09-18 21:42 UTC (permalink / raw)
  To: emacs-orgmode

Matt Price <moptop99@gmail.com> writes:

> This is fun, thanks John. I really like the plist version put would
> also like to loop through the variables in a let statement somehow.
>
> I think what I'm missing is the equivalent of a javascript implicit
> destructuring construct:
>
> let { } = object;
>
> which will define new variables prop1, prop2... forever enumerable
> property of the object.  Is thre away to do that kind of destructuring
> bind -- which binds *everything* in the plist, without knowing the
> symbol names in advance? that would be really great.

In fact, he has written an article about that sort of thing, which you
can find linked here, along with some other destructuring tools:

https://github.com/alphapapa/emacs-package-dev-handbook#a-callable-plist-data-structure-for-emacs
https://github.com/alphapapa/emacs-package-dev-handbook#with-dict-with-plist-vals-1

Note that automatically binding variables named according to plist keys
which are not known in advance would have to happen at runtime and would
require use of eval, as well as potentially overriding variables that
you're already using.

Instead, I recommend using -let, which has a &plist keyword, which you
can use like:

  (-let* (((&plist :query :preamble :preamble-case-fold) (org-ql--query-preamble query)))
    (list query preamble preamble-case-fold))

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-18 21:26         ` Joost Kremers
@ 2019-09-18 21:44           ` Adam Porter
  2019-09-18 23:17             ` John Kitchin
  0 siblings, 1 reply; 11+ messages in thread
From: Adam Porter @ 2019-09-18 21:44 UTC (permalink / raw)
  To: emacs-orgmode

Joost Kremers <joostkremers@fastmail.fm> writes:

> On Wed, Sep 18 2019, Matt Price wrote:
>> Is thre away to do that kind of destructuring bind -- which
>> binds *everything* in the plist, without knowing the symbol names in
>> advance? that would be really great.
>
> let-alist perhaps?

Well, let-alist is for alists, not plists.  ;) But anyway, it's a macro,
and it does require knowing keys at compile time.  -let is a good
alternative for plists and other maps.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-18 21:44           ` Adam Porter
@ 2019-09-18 23:17             ` John Kitchin
  2019-09-19  0:10               ` Adam Porter
  0 siblings, 1 reply; 11+ messages in thread
From: John Kitchin @ 2019-09-18 23:17 UTC (permalink / raw)
  To: Adam Porter; +Cc: org-mode-email

[-- Attachment #1: Type: text/plain, Size: 1519 bytes --]

I played with a similar idea of converting a plist to something you can
call to access values at
https://kitchingroup.cheme.cmu.edu/blog/2017/04/16/A-callable-plist-data-structure-for-Emacs/.
It did end up as a macro, but no eval required. It never made it past that
post, but it might have an application here.

I am not sure why you have to loop over everything in a let statement
though. you can use something like https://github.com/nicferrier/emacs-kv to
get all the keys an loop over those to do what you want, or you can just
use cl-loop to do that. So, unless you are defining new variables for
readability you shouldn't need to let bind anything that is in the plist,
only new things that are derived from it.

John

-----------------------------------
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



On Wed, Sep 18, 2019 at 5:45 PM Adam Porter <adam@alphapapa.net> wrote:

> Joost Kremers <joostkremers@fastmail.fm> writes:
>
> > On Wed, Sep 18 2019, Matt Price wrote:
> >> Is thre away to do that kind of destructuring bind -- which
> >> binds *everything* in the plist, without knowing the symbol names in
> >> advance? that would be really great.
> >
> > let-alist perhaps?
>
> Well, let-alist is for alists, not plists.  ;) But anyway, it's a macro,
> and it does require knowing keys at compile time.  -let is a good
> alternative for plists and other maps.
>
>
>

[-- Attachment #2: Type: text/html, Size: 2436 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: lisp: scoping vars in repetitive defuns
  2019-09-18 23:17             ` John Kitchin
@ 2019-09-19  0:10               ` Adam Porter
  0 siblings, 0 replies; 11+ messages in thread
From: Adam Porter @ 2019-09-19  0:10 UTC (permalink / raw)
  To: emacs-orgmode

John Kitchin <jkitchin@andrew.cmu.edu> writes:

> I am not sure why you have to loop over everything in a let statement
> though. you can use something like
> https://github.com/nicferrier/emacs-kv to get all the keys an loop
> over those to do what you want, or you can just use cl-loop to do
> that.

Recent Emacs versions also have, e.g. map-keys from map.el.

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2019-09-19  0:10 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-09-17 11:30 lisp: scoping vars in repetitive defuns Matt Price
2019-09-17 12:46 ` John Kitchin
2019-09-18  1:12   ` Matt Price
2019-09-18 12:48     ` John Kitchin
2019-09-18 20:52       ` Matt Price
2019-09-18 21:26         ` Joost Kremers
2019-09-18 21:44           ` Adam Porter
2019-09-18 23:17             ` John Kitchin
2019-09-19  0:10               ` Adam Porter
2019-09-18 21:42         ` Adam Porter
2019-09-17 13:42 ` Nick Dokos

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).