;;; Log of querying the obarray in class, see a prior log for commented code ;;; https://cosc59.gitlab.io/lisp/obarray-simple-examples-log.txt obarray [0 0 pr-ps-file-preview timer-next-integral-multiple-of-time vietnamese-tcvn-unix 0 0 make-mode-line-mouse-map backquote-listify access-label selected c-electric-lt-gt ...] (length obarray) 15121 ;; getting symbols from obarray into a list hanging on a global symbol. ;; this is poor style, as it pollutes the global symbol namespace unnecessarily (setq gsyms nil) nil (mapatoms (lambda (x) (setq gsyms (cons x gsyms)))) nil ; mapatoms always returns nil (length gsyms) 19980 gsyms (utf-16-le-dos pr-ps-file-preview utf-7-mac timer-next-integral-multiple-of-time :log vietnamese-tcvn-unix make-mode-line-mouse-map backquote-listify sgml-mode access-label non-ascii selected ...) ;; we can test which elements of this list are functions (boundp '+) ; no symbol value nil (fboundp '+) ; function value! t ;;; collect only symbols with a function value bound to them (setq fgsyms nil) nil (mapatoms (lambda (x) (if (fboundp x) (setq fgsyms (cons x fgsyms))))) nil fgsyms (pr-ps-file-preview timer-next-integral-multiple-of-time make-mode-line-mouse-map backquote-listify sgml-mode font-put help-follow-symbol c-electric-lt-gt dolist cpp-parse-edit active-minibuffer-window uniquify-rename-buffer ...) (length fgsyms) 9558 ; that's a lot of functions, but clearly there are fewer functions that symbols by about a half (require 'cl-lib) ; this will give us the cl-set-difference function cl-lib (cl-set-difference gsyms fgsyms)) (utf-16-le-dos utf-7-mac :log vietnamese-tcvn-unix access-label non-ascii selected emacs-mule-unix c-state-min-scan-pos cc-vars compilation-disable-input font-lock-syntactically-fontified ...) (length (cl-set-difference gsyms fgsyms)) 10422 ;; so how many of these are not bound as either symbols or functions? Quite a few! (length (seq-filter (lambda (x) (and (not (boundp x)) (not (fboundp x)))) gsyms)) 6591 ;;; Polluting the global symbol space is nasty. Let's do it right and collect results ;;; in a local variable. This variable will survive as the return value of let: (defun collect-all-atoms () (let ((globsyms nil)) (mapatoms (lambda (x) (setq globsyms (cons x globsyms)))) globsyms)) collect-all-atoms (length (collect-all-atoms)) 19984 ;; let's also write a find of filter with a test function as an explicit argument (defun collect-all-atoms-that-match ( func ) (let ((globsyms nil)) (mapatoms (lambda (x) (if (funcall func x) (setq globsyms (cons x globsyms))))) globsyms)) collect-all-atoms-that-match (length (collect-all-atoms-that-match (lambda (x) (fboundp x)))) 9560 ;; We could be more succinct: (length (collect-all-atoms-that-match #'fboundp)) 9560 (length (collect-all-atoms-that-match 'boundp)) 4293 ;; The + function should be in the function-found list. Indeed it is: (member '+ (collect-all-atoms-that-match 'fboundp)) (+ indian-compose-string easy-mmode-define-global-mode isearch-message-prefix - / cl-find-if-not set-upcase-syntax completion--done blink-cursor--should-blink isearch-forward format-mode-line ...) ;; member is a fun function, returns the cons cell where the sought-after symbol occurs in the list. ;; We can write our own: (defun our-member (x l) (cond ((null l) nil) ((consp l) (if (eq x (car l)) l (our-member x (cdr l)))) (t (error "No atoms allowed here")))) our-member (our-member '1 '(1 2 3 4)) (1 2 3 4) (our-member '2 '(1 2 3 4)) (2 3 4) (our-member '5 '(1 2 3 4)) nil (our-member '+ fgsyms) (+ indian-compose-string easy-mmode-define-global-mode isearch-message-prefix - / cl-find-if-not set-upcase-syntax completion--done blink-cursor--should-blink isearch-forward format-mode-line ...) ;;; An aside about symbols: they are created and inserted into the obarray by (read) of ;;; the read-eval-print loop. So by the time we check the new snapshot of the obarray, ;;; any symbol mentioned in the arguments of (member) is already there! :) (member 'x (collect-all-atoms)) (x bookmark-set-no-overwrite set-keymap-parent set-cursor-color help-xref-mule-regexp-template completion-in-region--single-word tramp-ignored-file-name-regexp change-major-mode-hook | tab-first-completion new-fontset cl-sublis ...) (boundp 'x) nil (member 'y (collect-all-atoms)) (y help-xref-mule-regexp-template completion-in-region--single-word tramp-ignored-file-name-regexp change-major-mode-hook | tab-first-completion new-fontset cl-sublis file-name-as-directory project-search isearch ...) (member 'z (collect-all-atoms)) (z tramp-ignored-file-name-regexp change-major-mode-hook | tab-first-completion new-fontset cl-sublis file-name-as-directory project-search isearch query-replace-help greek-iso-8bit-unix ...) (member 'zebra (collect-all-atoms)) (zebra semiexpanded Evaluate\ Buffer flyspell-large-region-buffer help-echo pmem pahawh-hmong byte-current-buffer setq-default with-eval-after-load dired-other-tab tibetan-precomposition-rule-regexp ...) ;;; MAP is an all-important idiom of functional programming with lists. It's called mapcar ;;; in classic Lisp (mapcar (lambda (x) (* x 2)) '(1 2 3 4 5)) (2 4 6 8 10) ;;; Let's write our own version of MAP (defun our-map (func xs) (cond ((null xs) nil) ((consp xs) (cons (funcall func (car xs)) (our-map func (cdr xs)))))) our-map (our-map (lambda (x) (* x 2)) '(1 2 3 4 5)) (2 4 6 8 10) ;; this may run out of eLisp's stack space. We'll deal with this later, using tail calls. (our-map #'fboundp fgsyms) ;;; Let's look at what (lambda (..) ..) creates. It's a "closure": (lambda (x) (+ 1 x)) (closure (t) (x) (+ 1 x)) ;;; We can do a map over all symbols, just to practice passing an arbitrary function ;;; as an argument: (defun map-symbols (fn) "Return a list of all symbols in obarray" (let ((syms nil)) ; local list for a snapshot of obarray (mapatoms (lambda (s) (setq syms (cons (funcall fn s) syms)))) syms)) map-symbols (filter (map-symbols (lambda (x) x)) (utf-16-le-dos pr-ps-file-preview utf-7-mac timer-next-integral-multiple-of-time :log vietnamese-tcvn-unix make-mode-line-mouse-map backquote-listify sgml-mode non-ascii selected font-put ...) ;; Closures are interesting. This example is from ;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html (defvar my-ticker nil) (let ((x 0)) ; x is lexically bound. (setq my-ticker (lambda () (setq x (1+ x))))) my-ticker (closure ((x . 0) t) nil (setq x (1+ x))) (funcall my-ticker) 1 (funcall my-ticker) 2 (funcall my-ticker) 3 my-ticker (closure ((x . 3) t) nil (setq x (1+ x))) ;; <-- notice (x . 3) saved with the function body ;; Let's see how closures are actually implemented. For that we'll need to look ;;; into the bytecode that eLisp is compiled into, described in ;;; https://github.com/rocky/elisp-bytecode ;;; with a handy PDF at http://rocky.github.io/elisp-bytecode.pdf ;;; More here if you are curious: https://nullprogram.com/blog/2014/01/04/ ;;; https://nullprogram.com/blog/2013/12/30/ (defvar g 100) g (disassemble (lambda () (let ((g 1)) g))) byte code: args: nil 0 constant 1 1 return (disassemble my-ticker) byte code: args: nil 0 constant (3) 1 dup 2 car-safe 3 add1 4 setcar 5 return (funcall my-ticker) 4 my-ticker (closure ((x . 4) t) nil (setq x (1+ x))) (disassemble my-ticker) nil byte code: args: nil 0 constant (4) 1 dup 2 car-safe 3 add1 4 setcar 5 return (disassemble (lambda (z) (let ((x g)) (let ((y x)) (+ z y))))) byte code: doc: ... args: (arg1) 0 varref g 1 stack-ref 1 2 stack-ref 1 3 plus 4 return ;;; ----- we stopped here in class