diff --git a/README.md b/README.md index fd3b3ca..1f18676 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ This is so much a work in progress that you should definitely not be using it ye ## Anaphoric functions -While `!filter` takes a function to filter the list by, you can also pass -it a form - which will then be executed with `it` exposed as the list item. -Here's an example: +While `!filter` takes a function to filter the list by, you can also use the +anaphoric form with double bangs - which will then be executed with `it` exposed +as the list item. Here's an example: (!filter (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) ;; normal version - (!filter (= 0 (% it 2)) '(1 2 3 4)) ;; anaphoric version + (!!filter (= 0 (% it 2)) '(1 2 3 4)) ;; anaphoric version of course the original can also be written like diff --git a/bang.el b/bang.el index 085f76f..5b1bc50 100644 --- a/bang.el +++ b/bang.el @@ -1,4 +1,4 @@ -;;; bang.el --- A modern list library for Emacs +;;; bang.el --- A modern list library for Emacs -*- lexical-binding: t -*- ;; Copyright (C) 2012 Magnar Sveen, Joel McCracken @@ -25,96 +25,94 @@ ;;; Code: -(defun !--call-with-it (form-or-fn) - (if (functionp form-or-fn) - (list form-or-fn 'it) - form-or-fn)) - -(defmacro !map (form-or-fn list) - "Returns a new list consisting of the result of applying -FORM-OR-FN to the items in list." - (if (functionp form-or-fn) - `(mapcar #',form-or-fn ,list) - `(mapcar #'(lambda (it) ,form-or-fn) ,list))) - -(defmacro !reduce-from (form-or-fn initial-value list) - "Returns the result of applying FORM-OR-FN to INITIAL-VALUE and -the first item in LIST, then applying FORM-OR-FN to that result -and the 2nd item, etc. If INITIAL-VALUE contains no items, -returns INITIAL-VALUE and FORM-OR-FN is not called." - `(let ((!--list ,list) - (!--acc ,initial-value)) - (while !--list - (let ((it (car !--list)) - (acc !--acc)) - (setq !--acc ,(if (functionp form-or-fn) (list form-or-fn 'acc 'it) form-or-fn)) - (setq !--list (cdr !--list)))) - !--acc)) - -(defmacro !reduce (form-or-fn list) - "Returns the result of applying FORM-OR-FN to the first 2 items in LIST, -then applying FORM-OR-FN to that result and the 3rd item, etc. If -LIST contains no items, FORM-OR-FN must accept no arguments as -well, and reduce returns the result of calling FORM-OR-FN with no -arguments. If LIST has only 1 item, it is returned and FORM-OR-FN -is not called." - (if (eval list) - `(!reduce-from ,form-or-fn ,(car (eval list)) ',(cdr (eval list))) - (if (functionp form-or-fn) - (list form-or-fn) - `(let (acc it) ,form-or-fn)))) - -(defmacro !filter (form-or-fn list) - "Returns a new list of the items in LIST for which FORM-OR-FN returns a non-nil value." - `(let ((!--list ,list) - (!--result '())) - (while !--list - (let ((it (car !--list))) - (when ,(!--call-with-it form-or-fn) - (setq !--result (cons it !--result)))) - (setq !--list (cdr !--list))) - (nreverse !--result))) - -(defmacro !remove (form-or-fn list) - "Returns a new list of the items in LIST for which FORM-OR-FN returns nil." - `(!filter (not ,(!--call-with-it form-or-fn)) ,list)) - -(defalias '!select '!filter) -(defalias '!reject '!remove) +(defalias '!map 'mapcar) + +(defmacro !!map (form list) + `(!map (lambda (it) ,form) ,list)) + +(defun !reduce-from (fn initial-value list) + "Returns the result of applying FN to INITIAL-VALUE and the +first item in LIST, then applying FN to that result and the 2nd +item, etc. If INITIAL-VALUE contains no items, returns +INITIAL-VALUE and FN is not called." + (let ((acc initial-value)) + (while list + (setq acc (funcall fn acc (car list))) + (setq list (cdr list))) + acc)) + +(defmacro !!reduce-from (form initial-value list) + `(!reduce-from (lambda (acc it) ,form) ,initial-value ,list)) + +(defun !reduce (fn list) + "Returns the result of applying FN to the first 2 items in LIST, +then applying FN to that result and the 3rd item, etc. If LIST +contains no items, FN must accept no arguments as well, and +reduce returns the result of calling FN with no arguments. If +LIST has only 1 item, it is returned and FN is not called." + (if list + (!reduce-from fn (car list) (cdr list)) + (funcall fn))) + +(defmacro !!reduce (form list) + `(!reduce (lambda (&optional acc it) ,form) ,list)) + +(defun !filter (fn list) + "Returns a new list of the items in LIST for which FN returns a non-nil value." + (let ((result '())) + (while list + (when (funcall fn (car list)) + (setq result (cons (car list) result))) + (setq list (cdr list))) + (nreverse result))) + +(defmacro !!filter (form list) + `(!filter (lambda (it) ,form) ,list)) + +(defun !remove (fn list) + "Returns a new list of the items in LIST for which FN returns nil." + (!!filter (not (funcall fn it)) list)) + +(defmacro !!remove (form list) + `(!!filter (not ,form) ,list)) (defun !concat (&rest lists) "Returns a new list with the concatenation of the elements in the supplied LISTS." (apply 'append (append lists '(nil)))) -(defmacro !partial (fn &rest args) - "Takes a function FN and fewer than the normal arguments to FN, and - returns a fn that takes a variable number of additional ARGS. When - called, the returned function calls FN with args + additional args." - `(apply-partially ',fn ,@args)) - -(defmacro !mapcat (fn list) +(defun !mapcat (fn list) "Returns the result of applying concat to the result of applying map to FN and LIST. Thus function FN should return a collection." - `(apply '!concat (!map ,fn ,list))) + (apply '!concat (!map fn list))) + +(defmacro !!mapcat (form list) + `(!mapcat (lambda (it) ,form) ,list)) + +(defalias '!partial 'apply-partially) (defun !uniq (list) "Return a new list with all duplicates removed. The test for equality is done with `equal', or with `!compare-fn' if that's non-nil." - (!filter (not (!contains? !--result it)) list)) + (let ((result '())) + (while list + (when (not (!contains? result (car list))) + (setq result (cons (car list) result))) + (setq list (cdr list))) + (nreverse result))) (defun !intersection (list list2) "Return a new list containing only the elements that are members of both LIST and LIST2. The test for equality is done with `equal', or with `!compare-fn' if that's non-nil." - (!filter (!contains? list2 it) list)) + (!!filter (!contains? list2 it) list)) (defun !difference (list list2) "Return a new list with only the members of LIST that are not in LIST2. The test for equality is done with `equal', or with `!compare-fn' if that's non-nil." - (!filter (not (!contains? list2 it)) list)) + (!!filter (not (!contains? list2 it)) list)) (defun !contains? (list element) "Return whether LIST contains ELEMENT. diff --git a/tests.el b/tests.el index 9bce0f3..a197849 100644 --- a/tests.el +++ b/tests.el @@ -7,70 +7,82 @@ (ert-deftest map () "`!map' returns a new list with the results of calling the function on each element." (should (equal (!map (lambda (num) (* num num)) '(1 2 3 4)) '(1 4 9 16))) - (should (equal (!map (* it it) '(1 2 3 4)) '(1 4 9 16))) - (should (equal (!map square '(1 2 3 4)) '(1 4 9 16)))) + (should (equal (!map 'square '(1 2 3 4)) '(1 4 9 16))) + (should (equal (!!map (* it it) '(1 2 3 4)) '(1 4 9 16))) + ) -(ert-deftest reduce () - "`!reduce' takes a list and applies the function over them to create one result" - (should (equal (!reduce + '()) 0)) - (should (equal (!reduce + '(1)) 1)) - (should (equal (!reduce + '(1 2)) 3)) - (should (equal (!reduce-from + 7 '()) 7)) - (should (equal (!reduce-from + 7 '(1)) 8)) - (should (equal (!reduce-from + 7 '(1 2)) 10)) +(ert-deftest reduce-from () + "`!reduce-from' takes a list and an initial value, and applies the function over them to create one result" + (should (equal (!reduce '+ '()) 0)) + (should (equal (!reduce '+ '(1)) 1)) + (should (equal (!reduce '+ '(1 2)) 3)) + (should (equal (!reduce-from '+ 7 '()) 7)) + (should (equal (!reduce-from '+ 7 '(1)) 8)) + (should (equal (!reduce-from '+ 7 '(1 2)) 10)) + (should (equal (!!reduce-from (+ acc it) 7 '(1 2 3)) 13)) + ) +(ert-deftest reduce () + "`!reduce' takes a list and applies the function over the elements to create one result" (should (equal (!reduce (lambda (memo item) (format "%s-%s" memo item)) '(1 2 3)) "1-2-3")) - (should (equal (!reduce (format "%s-%s" acc it) '(1 2 3)) "1-2-3")) - (should (equal (!reduce (format "%s-%s" acc it) '()) "nil-nil"))) + (should (equal (!!reduce (format "%s-%s" acc it) '(1 2 3)) "1-2-3")) + (should (equal (!!reduce (format "%s-%s" acc it) '()) "nil-nil")) + ) (ert-deftest filter () "`!filter' returns a new list of only those elements where the predicate was non-nil." (should (equal (!filter (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) '(2 4))) - (should (equal (!filter (= 0 (% it 2)) '(1 2 3 4)) '(2 4))) - (should (equal (!filter even? '(1 2 3 4)) '(2 4))) - (should (equal (!select even? '(1 2 3 4)) '(2 4)))) + (should (equal (!filter 'even? '(1 2 3 4)) '(2 4))) + (should (equal (!!filter (= 0 (% it 2)) '(1 2 3 4)) '(2 4))) + ) (ert-deftest remove () "`!remove' returns a new list of only those elements where the predicate was nil." (should (equal (!remove (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) '(1 3))) - (should (equal (!remove (= 0 (% it 2)) '(1 2 3 4)) '(1 3))) - (should (equal (!remove even? '(1 2 3 4)) '(1 3))) - (should (equal (!reject even? '(1 2 3 4)) '(1 3)))) + (should (equal (!remove 'even? '(1 2 3 4)) '(1 3))) + (should (equal (!!remove (= 0 (% it 2)) '(1 2 3 4)) '(1 3))) + ) (ert-deftest concat () "`!concat' returns the concatenation of the elements in the supplied lists" (should (equal (!concat) nil)) (should (equal (!concat '(1)) '(1))) (should (equal (!concat '(1) '(2)) '(1 2))) - (should (equal (!concat '(1) '(2 3) '(4)) '(1 2 3 4)))) + (should (equal (!concat '(1) '(2 3) '(4)) '(1 2 3 4))) + ) (ert-deftest mapcat () "`!mapcat' applies the function to all elements of the list and then concatenates the result" - (should (equal (!mapcat list '(1 2 3)) '(1 2 3))) + (should (equal (!mapcat 'list '(1 2 3)) '(1 2 3))) (should (equal (!mapcat (lambda (item) (list 0 item)) '(1 2 3)) '(0 1 0 2 0 3))) - (should (equal (!mapcat (list 0 it) '(1 2 3)) '(0 1 0 2 0 3)))) + (should (equal (!!mapcat (list 0 it) '(1 2 3)) '(0 1 0 2 0 3))) + ) (ert-deftest partial () "`!partial' returns a function like fn where the first arguments are filled in" - (should (equal (funcall (!partial + 5) 3) 8)) - (should (equal (funcall (!partial + 5 2) 3) 10))) + (should (equal (funcall (!partial '+ 5) 3) 8)) + (should (equal (funcall (!partial '+ 5 2) 3) 10)) + ) (ert-deftest difference () "`!difference' returns a new list of only elements in list1 that are not in list2." (should (equal (!difference '() '()) '())) (should (equal (!difference '(1 2 3) '(4 5 6)) '(1 2 3))) - (should (equal (!difference '(1 2 3 4) '(3 4 5 6)) '(1 2)))) + (should (equal (!difference '(1 2 3 4) '(3 4 5 6)) '(1 2))) + ) (ert-deftest intersection () "`!intersection' returns a new list of only elements that are in both given lists." (should (equal (!intersection '() '()) '())) (should (equal (!intersection '(1 2 3) '(4 5 6)) '())) - (should (equal (!intersection '(1 2 3 4) '(3 4 5 6)) '(3 4)))) + (should (equal (!intersection '(1 2 3 4) '(3 4 5 6)) '(3 4))) + ) (ert-deftest uniq () "`!uniq' returns a new list of only unique elements." (should (equal (!uniq '()) '())) - (should (equal (!uniq '(1 2 2 4)) '(1 2 4)))) + (should (equal (!uniq '(1 2 2 4)) '(1 2 4))) + ) (ert-deftest contains? () "`!contains?' returns t if the list contains the element." @@ -78,4 +90,5 @@ (should (!contains? '(1 2 3) 2)) (should (not (!contains? '() '()))) (should (not (!contains? '() 1))) - (should (not (!contains? '(1 2 4) 3)))) + (should (not (!contains? '(1 2 4) 3))) + )