all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* add-to-list with lexical variables
@ 2013-06-08 12:39 Hongxu Chen
  2013-06-08 14:00 ` Pascal J. Bourguignon
  0 siblings, 1 reply; 6+ messages in thread
From: Hongxu Chen @ 2013-06-08 12:39 UTC (permalink / raw)
  To: help-gnu-emacs


Hi list,

  I am writing a snippet to add element into environment variables, and
  it is written as below:

#+BEGIN_SRC elisp  
(defun no-dup-add-env-ele (env env-ele-string)
   (let* ((env-separator (if (string-equal system-type "windows-nt") ";" ":"))
     (env-list (split-string (getenv env) env-separator)))
     (if (string-match-p env-separator env-ele-string)
        (dolist (env-ele (split-string env-ele-string env-separator))
                (add-to-list 'env-list env-ele)) 
     (add-to-list 'env-list env-ele-string))
     (setenv env (mapconcat 'identity env-list ":"))))
#+END_SRC

1. when I set `lexical-binding' to t and byte-compile the file, it
would report this error:

    add-to-list cannot use lexical var `env-list'

2. And when I using `lexical-let*' instead, there would be an warning:

    Warning: assignment to free variable `env-list'

3. However after resetting `lexical-binding' to nil, byte-compiles well.

So what are the differences?

Thanks,
Hongxu Chen




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

* Re: add-to-list with lexical variables
  2013-06-08 12:39 add-to-list with lexical variables Hongxu Chen
@ 2013-06-08 14:00 ` Pascal J. Bourguignon
  2013-06-08 15:19   ` Hongxu Chen
       [not found]   ` <mailman.1222.1370704797.22516.help-gnu-emacs@gnu.org>
  0 siblings, 2 replies; 6+ messages in thread
From: Pascal J. Bourguignon @ 2013-06-08 14:00 UTC (permalink / raw)
  To: help-gnu-emacs

Hongxu Chen <leftcopy.chx@gmail.com> writes:

> Hi list,
>
>   I am writing a snippet to add element into environment variables, and
>   it is written as below:
>
> #+BEGIN_SRC elisp  
> (defun no-dup-add-env-ele (env env-ele-string)
>    (let* ((env-separator (if (string-equal system-type "windows-nt") ";" ":"))
>      (env-list (split-string (getenv env) env-separator)))
>      (if (string-match-p env-separator env-ele-string)
>         (dolist (env-ele (split-string env-ele-string env-separator))
>                 (add-to-list 'env-list env-ele)) 
>      (add-to-list 'env-list env-ele-string))
>      (setenv env (mapconcat 'identity env-list ":"))))
> #+END_SRC
>
> 1. when I set `lexical-binding' to t and byte-compile the file, it
> would report this error:
>
>     add-to-list cannot use lexical var `env-list'
>
> 2. And when I using `lexical-let*' instead, there would be an warning:
>
>     Warning: assignment to free variable `env-list'
>
> 3. However after resetting `lexical-binding' to nil, byte-compiles well.
>
> So what are the differences?

The difference is not.

    (not t) --> nil 
    (not nil) --> t

or:

    not lexical binding is dynamic binding.
    not dynamic binding is lexical binding.


Now, to add new elements to a list bound to some place, there's the
pushnew cl operator (a macro).  push can be used to push
unconditionnaly, and pop to remove the first element from a list bound
to a place.  That's how things have to be done with lexical binding.


(require 'cl)

(defun* pushnew/envvar (env-ele-string env &key (test (function equal)))
  (let* ((env-separator (if (member system-type '(windows-nt ms-dos)) ";" ":"))
         (env-list      (split-string (or (getenv env) "") env-separator t)))
    (dolist (env-ele (split-string env-ele-string env-separator))
      (pushnew env-ele env-list :test test))
    (setenv env (mapconcat 'identity env-list env-separator))))

(defun test/pushnew/envvar ()
  (setenv "TEST" nil)
  (assert (equal (pushnew/envvar "A:B:C" "TEST")
                 "C:B:A"))
  (assert (equal (getenv "TEST")
                 "C:B:A"))
  (assert (equal (pushnew/envvar "D:E:F" "TEST")
                 "F:E:D:C:B:A"))
  (assert (equal (getenv "TEST")
                 "F:E:D:C:B:A"))
  (assert (equal (pushnew/envvar "G" "TEST")
                 "G:F:E:D:C:B:A"))
  (assert (equal (getenv "TEST")
                 "G:F:E:D:C:B:A"))
  (assert (equal (pushnew/envvar "G:F:E:D:C:B:A" "TEST")
                 "G:F:E:D:C:B:A"))
  (assert (equal (pushnew/envvar "X:F:Y:D:Z:B" "TEST")
                 "Z:Y:X:G:F:E:D:C:B:A"))
  (assert (equal (pushnew/envvar "a:b:c:d" "TEST")
                 "d:c:b:a:Z:Y:X:G:F:E:D:C:B:A"))
  (assert (equal (pushnew/envvar "x:y:z" "TEST" :test (function equalp))
                 "d:c:b:a:Z:Y:X:G:F:E:D:C:B:A"))
  :success)

(test/pushnew/envvar)
--> :success


You have to add a test argument, since for some environment variables,
and for some values, you may want to do case insensitive, or more
complex comparison.

For example, if you mount a MS-DOS file system on a case sensitive unix file
system, itself mounted a HFS+ case insensitive file system, you will
have to compare parts of the path case sensitively, and parts case
insensitively:


mount /dev/disk1s1 /Volumes/case-sensitive
mount /dev/disk2s1 /Volumes/case-sensitive/mnt/ms-dos

Now, /VOLUMES/case-sensitive/mnt/ms-dos/DESCENT
is the same path as:
     /Volumes/case-sensitive/mnt/ms-dos/Descent
but not the same as:
     /Volumes/case-sensitive/MNT/ms-dos/descent
Perhaps there are both
     /Volumes/case-sensitive/MNT 
and:
     /Volumes/case-sensitive/mnt 
on the case sensitive file system!

so you will have to write:

(pushnew/envvar "/Volumes/case-sensitive/mnt/ms-dos/Descent"
                "PATH"
                :test (function file-system-sensitive-path-equal-p))

with:

(defun file-system-sensitive-path-equal-p (a b)
  (labels ((compare-path (curdir ac bc)
             (cond
               ((null ac) (null bc))
               ((null bc) nil)
               (t
                (and (funcall (if (case-sensitive-file-system-at-path-p curdir)
                                  (function equal)
                                  (function equalp))
                              (car ac) (car ab))
                     (compare-path (path-append curdir (car ac))
                                   (cdr ac) (cdr bc)))))))
    (compare-path "/"
                  (split-path (expand-file-name a))
                  (split-path (expand-file-name b)))))


So that if there's already

    /Volumes/case-sensitive/mnt/ms-dos/DESCENT

on PATH,

    (pushnew/envvar "/VOLUMES/case-sensitive/mnt/ms-dos/Descent"
                    "PATH"
                    :test (function file-system-sensitive-path-equal-p))

won't add it, but:

    (pushnew/envvar "/Volumes/case-sensitive/mnt/MS-DOS/DESCENT"
                    "PATH"
                    :test (function file-system-sensitive-path-equal-p))

will.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
A bad day in () is better than a good day in {}.
You can take the lisper out of the lisp job, but you can't take the lisp out
of the lisper (; -- antifuchs




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

* Re: add-to-list with lexical variables
  2013-06-08 14:00 ` Pascal J. Bourguignon
@ 2013-06-08 15:19   ` Hongxu Chen
  2013-06-08 20:34     ` Stefan Monnier
       [not found]   ` <mailman.1222.1370704797.22516.help-gnu-emacs@gnu.org>
  1 sibling, 1 reply; 6+ messages in thread
From: Hongxu Chen @ 2013-06-08 15:19 UTC (permalink / raw)
  To: Pascal J. Bourguignon; +Cc: help-gnu-emacs

"Pascal J. Bourguignon" <pjb@informatimago.com> writes:

> Hongxu Chen <leftcopy.chx@gmail.com> writes:
>
>> Hi list,
>>
>>   I am writing a snippet to add element into environment variables, and
>>   it is written as below:
>>
>> #+BEGIN_SRC elisp  
>> (defun no-dup-add-env-ele (env env-ele-string)
>>    (let* ((env-separator (if (string-equal system-type "windows-nt") ";" ":"))
>>      (env-list (split-string (getenv env) env-separator)))
>>      (if (string-match-p env-separator env-ele-string)
>>         (dolist (env-ele (split-string env-ele-string env-separator))
>>                 (add-to-list 'env-list env-ele)) 
>>      (add-to-list 'env-list env-ele-string))
>>      (setenv env (mapconcat 'identity env-list ":"))))
>> #+END_SRC
>>
>> 1. when I set `lexical-binding' to t and byte-compile the file, it
>> would report this error:
>>
>>     add-to-list cannot use lexical var `env-list'
>>
>> 2. And when I using `lexical-let*' instead, there would be an warning:
>>
>>     Warning: assignment to free variable `env-list'
>>
>> 3. However after resetting `lexical-binding' to nil, byte-compiles well.
>>
>> So what are the differences?
>
> The difference is not.
>
>     (not t) --> nil 
>     (not nil) --> t
>
> or:
>
>     not lexical binding is dynamic binding.
>     not dynamic binding is lexical binding.
>
>
> Now, to add new elements to a list bound to some place, there's the
> pushnew cl operator (a macro).  push can be used to push
> unconditionnaly, and pop to remove the first element from a list bound
> to a place.  That's how things have to be done with lexical binding.
>
>
> (require 'cl)
>
> (defun* pushnew/envvar (env-ele-string env &key (test (function equal)))
>   (let* ((env-separator (if (member system-type '(windows-nt ms-dos)) ";" ":"))
>          (env-list      (split-string (or (getenv env) "") env-separator t)))
>     (dolist (env-ele (split-string env-ele-string env-separator))
>       (pushnew env-ele env-list :test test))
>     (setenv env (mapconcat 'identity env-list env-separator))))
>
> (defun test/pushnew/envvar ()
>   (setenv "TEST" nil)
>   (assert (equal (pushnew/envvar "A:B:C" "TEST")
>                  "C:B:A"))
>   (assert (equal (getenv "TEST")
>                  "C:B:A"))
>   (assert (equal (pushnew/envvar "D:E:F" "TEST")
>                  "F:E:D:C:B:A"))
>   (assert (equal (getenv "TEST")
>                  "F:E:D:C:B:A"))
>   (assert (equal (pushnew/envvar "G" "TEST")
>                  "G:F:E:D:C:B:A"))
>   (assert (equal (getenv "TEST")
>                  "G:F:E:D:C:B:A"))
>   (assert (equal (pushnew/envvar "G:F:E:D:C:B:A" "TEST")
>                  "G:F:E:D:C:B:A"))
>   (assert (equal (pushnew/envvar "X:F:Y:D:Z:B" "TEST")
>                  "Z:Y:X:G:F:E:D:C:B:A"))
>   (assert (equal (pushnew/envvar "a:b:c:d" "TEST")
>                  "d:c:b:a:Z:Y:X:G:F:E:D:C:B:A"))
>   (assert (equal (pushnew/envvar "x:y:z" "TEST" :test (function equalp))
>                  "d:c:b:a:Z:Y:X:G:F:E:D:C:B:A"))
>   :success)
>
> (test/pushnew/envvar)
> --> :success
>
>
> You have to add a test argument, since for some environment variables,
> and for some values, you may want to do case insensitive, or more
> complex comparison.
>
> For example, if you mount a MS-DOS file system on a case sensitive unix file
> system, itself mounted a HFS+ case insensitive file system, you will
> have to compare parts of the path case sensitively, and parts case
> insensitively:
>
>
> mount /dev/disk1s1 /Volumes/case-sensitive
> mount /dev/disk2s1 /Volumes/case-sensitive/mnt/ms-dos
>
> Now, /VOLUMES/case-sensitive/mnt/ms-dos/DESCENT
> is the same path as:
>      /Volumes/case-sensitive/mnt/ms-dos/Descent
> but not the same as:
>      /Volumes/case-sensitive/MNT/ms-dos/descent
> Perhaps there are both
>      /Volumes/case-sensitive/MNT 
> and:
>      /Volumes/case-sensitive/mnt 
> on the case sensitive file system!
>
> so you will have to write:
>
> (pushnew/envvar "/Volumes/case-sensitive/mnt/ms-dos/Descent"
>                 "PATH"
>                 :test (function file-system-sensitive-path-equal-p))
>
> with:
>
> (defun file-system-sensitive-path-equal-p (a b)
>   (labels ((compare-path (curdir ac bc)
>              (cond
>                ((null ac) (null bc))
>                ((null bc) nil)
>                (t
>                 (and (funcall (if (case-sensitive-file-system-at-path-p curdir)
>                                   (function equal)
>                                   (function equalp))
>                               (car ac) (car ab))
>                      (compare-path (path-append curdir (car ac))
>                                    (cdr ac) (cdr bc)))))))
>     (compare-path "/"
>                   (split-path (expand-file-name a))
>                   (split-path (expand-file-name b)))))
>
>
> So that if there's already
>
>     /Volumes/case-sensitive/mnt/ms-dos/DESCENT
>
> on PATH,
>
>     (pushnew/envvar "/VOLUMES/case-sensitive/mnt/ms-dos/Descent"
>                     "PATH"
>                     :test (function file-system-sensitive-path-equal-p))
>
> won't add it, but:
>
>     (pushnew/envvar "/Volumes/case-sensitive/mnt/MS-DOS/DESCENT"
>                     "PATH"
>                     :test (function file-system-sensitive-path-equal-p))
>
> will.

Thanks so much for your instruction. I now feel that there are really so
many flaws in my snippet. 

However I still don't know the difference `lexical-binding' and
`lexical-let' brings. Are there some authoritative introductions/tutorials?

Regards,
Hongxu Chen



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

* Re: add-to-list with lexical variables
       [not found]   ` <mailman.1222.1370704797.22516.help-gnu-emacs@gnu.org>
@ 2013-06-08 17:08     ` Pascal J. Bourguignon
  2013-06-09  3:18       ` Hongxu Chen
  0 siblings, 1 reply; 6+ messages in thread
From: Pascal J. Bourguignon @ 2013-06-08 17:08 UTC (permalink / raw)
  To: help-gnu-emacs

Hongxu Chen <leftcopy.chx@gmail.com> writes:

> However I still don't know the difference `lexical-binding' and
> `lexical-let' brings. Are there some authoritative introductions/tutorials?

http://www.emacswiki.org/emacs/DynamicBindingVsLexicalBinding

Also, any Common Lisp tutorial covers them.  


You may be interested by my explanations in the context of Common Lisp:
https://groups.google.com/group/comp.lang.lisp/msg/58f1c7ed53d0c0d6


But basically, it's a question of time vs. space.


Dynamic is change over time.  So dynamic binding will be concerned by
the time, by WHEN things are bound and referenced.


Lexical is textual.  So lexical binding will be concerned by space, by
WHERE in the text things are bound and referenced.


With dynamic binding, things occur at certain times:
#+BEGIN_SRC elisp  
;; with lexical-binding set to nil:
(let ((list (list 1 2 3)))
  (add-to-list 'list 0))
#+END_SRC

time
----

 1.  the function list is called with arguments 1 2 and 3.

 2.  it returns a list: (1 2 3).

 3.  let creates a variable, it saves any dynamic binding the symbol
     list may have, and it names the variable with the symbol list, and
     it binds it (dynamically, at this time 3.) to the list (1 2 3).

 4.  (quote list)  is evaluated.

 5.  it returns the symbol list.

 6.  0 is evaluated.

 7.  it returns 0

 8.  the function add-to-list is called with as arguments, the symbol
     list and the integer 0.

 9.  the function changes the dynamic binding of the symbol list.
     remember at this time, while the function add-to-list is being
     called, there symbol list is dynamically bound to the list (1 2 3),
     since the time 3.

10.  it returns, and the dynamic binding to the variable list has been
     changed.

11.  let destroys the dynamic binding of list, and destroys the variable
     named list.  Whatever dynamic binding list had before is restored.

 

With lexical binding, what matters is the space, the place where
expressions are:
#+BEGIN_SRC elisp  
;; with lexical-binding set to t:
(let ((list (list 1 2 3)))
  (add-to-list 'list 0))
#+END_SRC

(let ((list (list 1 2 3)))
  ;; From HERE
  (add-to-list 'list 0)
  ;; To HERE, there is a lexical variable named list.
  )

(defun add-to-list (variable value)
  ;; From HERE
  …
  … code of add-to-list
  …
  ;; To HERE, we're outside the lexical scope of the let form above. 
  ;; There is no lexical variable named list here!  Only the variables
  ;; and parameters of add-to-list.
  )

So there's no way for add-to-list to access directly the lexical
variable list defined in the let form.  

It could access it indirectly, if you passed it a closure.  See for
example:
http://www.informatimago.com/articles/usenet.html#C-like-pointers-in-Lisp
(those CL examples should run the same in emacs lisp with
lexical-binding set to t).


But since add-to-list is predefined not to use a closure, but to modify
a dynamic binding, it won't change a lexical binding.  It's not it's
purpose, it's not what it's designed to do.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
A bad day in () is better than a good day in {}.
You can take the lisper out of the lisp job, but you can't take the lisp out
of the lisper (; -- antifuchs


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

* Re: add-to-list with lexical variables
  2013-06-08 15:19   ` Hongxu Chen
@ 2013-06-08 20:34     ` Stefan Monnier
  0 siblings, 0 replies; 6+ messages in thread
From: Stefan Monnier @ 2013-06-08 20:34 UTC (permalink / raw)
  To: help-gnu-emacs

>>> 1. when I set `lexical-binding' to t and byte-compile the file, it
>>> would report this error:
>>> 
>>> add-to-list cannot use lexical var `env-list'

add-to-list is a function, and functions have access to the
dynamically-bound vars of its caller, but not to the lexically-bound
vars of its caller.

The warning was added because add-to-list is a frequent use-case where
Elisp code presumes dynamic binding.

>>> 2. And when I using `lexical-let*' instead, there would be an warning:
>>> 
>>> Warning: assignment to free variable `env-list'

lexical-let is an old way to get lexical binding.  It was added long
before add-to-list was invented, and it was only used at those few
places where people really wanted lexical binding, and those people
generally knew that add-to-list couldn't possibly work there, so the use
of add-to-list on a var bound with lexical-let was so rare that it
didn't occur to anybody to try and provide an ad-hoc check and warning.
instead it just notices that the call to `add-to-list' uses a (dynamic)
variable `env-list' which is not declared (i.e. is "free").

> However I still don't know the difference `lexical-binding' and
> `lexical-let' brings. Are there some authoritative introductions/tutorials?

lexical-let is an old hack made obsolete by lexical-binding.


        Stefan




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

* Re: add-to-list with lexical variables
  2013-06-08 17:08     ` Pascal J. Bourguignon
@ 2013-06-09  3:18       ` Hongxu Chen
  0 siblings, 0 replies; 6+ messages in thread
From: Hongxu Chen @ 2013-06-09  3:18 UTC (permalink / raw)
  To: Pascal J. Bourguignon; +Cc: help-gnu-emacs


Thanks Pascal and Stefan! It's so kind of you to tell me so much
about the scope/binding.

It's much clear to me now(although I think I need to read your words and
do some practice to fully grasp it).

"Pascal J. Bourguignon" <pjb@informatimago.com> writes:

> Hongxu Chen <leftcopy.chx@gmail.com> writes:
>
>> However I still don't know the difference `lexical-binding' and
>> `lexical-let' brings. Are there some authoritative introductions/tutorials?
>
> http://www.emacswiki.org/emacs/DynamicBindingVsLexicalBinding
>
> Also, any Common Lisp tutorial covers them.  
>
>
> You may be interested by my explanations in the context of Common Lisp:
> https://groups.google.com/group/comp.lang.lisp/msg/58f1c7ed53d0c0d6
>
>
> But basically, it's a question of time vs. space.
>
>
> Dynamic is change over time.  So dynamic binding will be concerned by
> the time, by WHEN things are bound and referenced.
>
>
> Lexical is textual.  So lexical binding will be concerned by space, by
> WHERE in the text things are bound and referenced.
>
>
> With dynamic binding, things occur at certain times:
> #+BEGIN_SRC elisp  
> ;; with lexical-binding set to nil:
> (let ((list (list 1 2 3)))
>   (add-to-list 'list 0))
> #+END_SRC
>
> time
> ----
>
>  1.  the function list is called with arguments 1 2 and 3.
>
>  2.  it returns a list: (1 2 3).
>
>  3.  let creates a variable, it saves any dynamic binding the symbol
>      list may have, and it names the variable with the symbol list, and
>      it binds it (dynamically, at this time 3.) to the list (1 2 3).
>
>  4.  (quote list)  is evaluated.
>
>  5.  it returns the symbol list.
>
>  6.  0 is evaluated.
>
>  7.  it returns 0
>
>  8.  the function add-to-list is called with as arguments, the symbol
>      list and the integer 0.
>
>  9.  the function changes the dynamic binding of the symbol list.
>      remember at this time, while the function add-to-list is being
>      called, there symbol list is dynamically bound to the list (1 2 3),
>      since the time 3.
>
> 10.  it returns, and the dynamic binding to the variable list has been
>      changed.
>
> 11.  let destroys the dynamic binding of list, and destroys the variable
>      named list.  Whatever dynamic binding list had before is restored.
>
>  
>
> With lexical binding, what matters is the space, the place where
> expressions are:
> #+BEGIN_SRC elisp  
> ;; with lexical-binding set to t:
> (let ((list (list 1 2 3)))
>   (add-to-list 'list 0))
> #+END_SRC
>
> (let ((list (list 1 2 3)))
>   ;; From HERE
>   (add-to-list 'list 0)
>   ;; To HERE, there is a lexical variable named list.
>   )
>
> (defun add-to-list (variable value)
>   ;; From HERE
>   …
>   … code of add-to-list
>   …
>   ;; To HERE, we're outside the lexical scope of the let form above. 
>   ;; There is no lexical variable named list here!  Only the variables
>   ;; and parameters of add-to-list.
>   )
>
> So there's no way for add-to-list to access directly the lexical
> variable list defined in the let form.  
>
> It could access it indirectly, if you passed it a closure.  See for
> example:
> http://www.informatimago.com/articles/usenet.html#C-like-pointers-in-Lisp
> (those CL examples should run the same in emacs lisp with
> lexical-binding set to t).
>
>
> But since add-to-list is predefined not to use a closure, but to modify
> a dynamic binding, it won't change a lexical binding.  It's not it's
> purpose, it's not what it's designed to do.

-- 
Regards,
Hongxu Chen



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

end of thread, other threads:[~2013-06-09  3:18 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-06-08 12:39 add-to-list with lexical variables Hongxu Chen
2013-06-08 14:00 ` Pascal J. Bourguignon
2013-06-08 15:19   ` Hongxu Chen
2013-06-08 20:34     ` Stefan Monnier
     [not found]   ` <mailman.1222.1370704797.22516.help-gnu-emacs@gnu.org>
2013-06-08 17:08     ` Pascal J. Bourguignon
2013-06-09  3:18       ` Hongxu Chen

Code repositories for project(s) associated with this external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.