help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: add-to-list with lexical variables


From: Hongxu Chen
Subject: Re: add-to-list with lexical variables
Date: Sat, 08 Jun 2013 23:19:42 +0800
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux)

"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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]