Use double-bang for anaphoric functions

master
Magnar Sveen 14 years ago
parent 73204ca935
commit d2dfb11d01
  1. 8
      README.md
  2. 134
      bang.el
  3. 67
      tests.el

@ -10,13 +10,13 @@ This is so much a work in progress that you should definitely not be using it ye
## Anaphoric functions ## Anaphoric functions
While `!filter` takes a function to filter the list by, you can also pass While `!filter` takes a function to filter the list by, you can also use the
it a form - which will then be executed with `it` exposed as the list item. anaphoric form with double bangs - which will then be executed with `it` exposed
Here's an example: as the list item. Here's an example:
(!filter (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) ;; normal version (!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 of course the original can also be written like

@ -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 ;; Copyright (C) 2012 Magnar Sveen, Joel McCracken
@ -25,96 +25,94 @@
;;; Code: ;;; Code:
(defun !--call-with-it (form-or-fn) (defalias '!map 'mapcar)
(if (functionp form-or-fn)
(list form-or-fn 'it) (defmacro !!map (form list)
form-or-fn)) `(!map (lambda (it) ,form) ,list))
(defmacro !map (form-or-fn list) (defun !reduce-from (fn initial-value list)
"Returns a new list consisting of the result of applying "Returns the result of applying FN to INITIAL-VALUE and the
FORM-OR-FN to the items in list." first item in LIST, then applying FN to that result and the 2nd
(if (functionp form-or-fn) item, etc. If INITIAL-VALUE contains no items, returns
`(mapcar #',form-or-fn ,list) INITIAL-VALUE and FN is not called."
`(mapcar #'(lambda (it) ,form-or-fn) ,list))) (let ((acc initial-value))
(while list
(defmacro !reduce-from (form-or-fn initial-value list) (setq acc (funcall fn acc (car list)))
"Returns the result of applying FORM-OR-FN to INITIAL-VALUE and (setq list (cdr list)))
the first item in LIST, then applying FORM-OR-FN to that result acc))
and the 2nd item, etc. If INITIAL-VALUE contains no items,
returns INITIAL-VALUE and FORM-OR-FN is not called." (defmacro !!reduce-from (form initial-value list)
`(let ((!--list ,list) `(!reduce-from (lambda (acc it) ,form) ,initial-value ,list))
(!--acc ,initial-value))
(while !--list (defun !reduce (fn list)
(let ((it (car !--list)) "Returns the result of applying FN to the first 2 items in LIST,
(acc !--acc)) then applying FN to that result and the 3rd item, etc. If LIST
(setq !--acc ,(if (functionp form-or-fn) (list form-or-fn 'acc 'it) form-or-fn)) contains no items, FN must accept no arguments as well, and
(setq !--list (cdr !--list)))) reduce returns the result of calling FN with no arguments. If
!--acc)) LIST has only 1 item, it is returned and FN is not called."
(if list
(defmacro !reduce (form-or-fn list) (!reduce-from fn (car list) (cdr list))
"Returns the result of applying FORM-OR-FN to the first 2 items in LIST, (funcall fn)))
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 (defmacro !!reduce (form list)
well, and reduce returns the result of calling FORM-OR-FN with no `(!reduce (lambda (&optional acc it) ,form) ,list))
arguments. If LIST has only 1 item, it is returned and FORM-OR-FN
is not called." (defun !filter (fn list)
(if (eval list) "Returns a new list of the items in LIST for which FN returns a non-nil value."
`(!reduce-from ,form-or-fn ,(car (eval list)) ',(cdr (eval list))) (let ((result '()))
(if (functionp form-or-fn) (while list
(list form-or-fn) (when (funcall fn (car list))
`(let (acc it) ,form-or-fn)))) (setq result (cons (car list) result)))
(setq list (cdr list)))
(defmacro !filter (form-or-fn list) (nreverse result)))
"Returns a new list of the items in LIST for which FORM-OR-FN returns a non-nil value."
`(let ((!--list ,list) (defmacro !!filter (form list)
(!--result '())) `(!filter (lambda (it) ,form) ,list))
(while !--list
(let ((it (car !--list))) (defun !remove (fn list)
(when ,(!--call-with-it form-or-fn) "Returns a new list of the items in LIST for which FN returns nil."
(setq !--result (cons it !--result)))) (!!filter (not (funcall fn it)) list))
(setq !--list (cdr !--list)))
(nreverse !--result))) (defmacro !!remove (form list)
`(!!filter (not ,form) ,list))
(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)
(defun !concat (&rest lists) (defun !concat (&rest lists)
"Returns a new list with the concatenation of the elements in "Returns a new list with the concatenation of the elements in
the supplied LISTS." the supplied LISTS."
(apply 'append (append lists '(nil)))) (apply 'append (append lists '(nil))))
(defmacro !partial (fn &rest args) (defun !mapcat (fn list)
"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)
"Returns the result of applying concat to the result of applying map to FN and LIST. "Returns the result of applying concat to the result of applying map to FN and LIST.
Thus function FN should return a collection." 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) (defun !uniq (list)
"Return a new list with all duplicates removed. "Return a new list with all duplicates removed.
The test for equality is done with `equal', The test for equality is done with `equal',
or with `!compare-fn' if that's non-nil." 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) (defun !intersection (list list2)
"Return a new list containing only the elements that are members of both LIST and 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', The test for equality is done with `equal',
or with `!compare-fn' if that's non-nil." or with `!compare-fn' if that's non-nil."
(!filter (!contains? list2 it) list)) (!!filter (!contains? list2 it) list))
(defun !difference (list list2) (defun !difference (list list2)
"Return a new list with only the members of LIST that are not in LIST2. "Return a new list with only the members of LIST that are not in LIST2.
The test for equality is done with `equal', The test for equality is done with `equal',
or with `!compare-fn' if that's non-nil." 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) (defun !contains? (list element)
"Return whether LIST contains ELEMENT. "Return whether LIST contains ELEMENT.

@ -7,70 +7,82 @@
(ert-deftest map () (ert-deftest map ()
"`!map' returns a new list with the results of calling the function on each element." "`!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 (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 () (ert-deftest reduce-from ()
"`!reduce' takes a list and applies the function over them to create one result" "`!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 '+ '()) 0))
(should (equal (!reduce + '(1)) 1)) (should (equal (!reduce '+ '(1)) 1))
(should (equal (!reduce + '(1 2)) 3)) (should (equal (!reduce '+ '(1 2)) 3))
(should (equal (!reduce-from + 7 '()) 7)) (should (equal (!reduce-from '+ 7 '()) 7))
(should (equal (!reduce-from + 7 '(1)) 8)) (should (equal (!reduce-from '+ 7 '(1)) 8))
(should (equal (!reduce-from + 7 '(1 2)) 10)) (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 (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) '(1 2 3)) "1-2-3"))
(should (equal (!reduce (format "%s-%s" acc it) '()) "nil-nil"))) (should (equal (!!reduce (format "%s-%s" acc it) '()) "nil-nil"))
)
(ert-deftest filter () (ert-deftest filter ()
"`!filter' returns a new list of only those elements where the predicate was non-nil." "`!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 (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 (!filter even? '(1 2 3 4)) '(2 4))) (should (equal (!!filter (= 0 (% it 2)) '(1 2 3 4)) '(2 4)))
(should (equal (!select even? '(1 2 3 4)) '(2 4)))) )
(ert-deftest remove () (ert-deftest remove ()
"`!remove' returns a new list of only those elements where the predicate was nil." "`!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 (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 (!remove even? '(1 2 3 4)) '(1 3))) (should (equal (!!remove (= 0 (% it 2)) '(1 2 3 4)) '(1 3)))
(should (equal (!reject even? '(1 2 3 4)) '(1 3)))) )
(ert-deftest concat () (ert-deftest concat ()
"`!concat' returns the concatenation of the elements in the supplied lists" "`!concat' returns the concatenation of the elements in the supplied lists"
(should (equal (!concat) nil)) (should (equal (!concat) nil))
(should (equal (!concat '(1)) '(1))) (should (equal (!concat '(1)) '(1)))
(should (equal (!concat '(1) '(2)) '(1 2))) (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 () (ert-deftest mapcat ()
"`!mapcat' applies the function to all elements of the list and then concatenates the result" "`!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 (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 () (ert-deftest partial ()
"`!partial' returns a function like fn where the first arguments are filled in" "`!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) 3) 8))
(should (equal (funcall (!partial + 5 2) 3) 10))) (should (equal (funcall (!partial '+ 5 2) 3) 10))
)
(ert-deftest difference () (ert-deftest difference ()
"`!difference' returns a new list of only elements in list1 that are not in list2." "`!difference' returns a new list of only elements in list1 that are not in list2."
(should (equal (!difference '() '()) '())) (should (equal (!difference '() '()) '()))
(should (equal (!difference '(1 2 3) '(4 5 6)) '(1 2 3))) (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 () (ert-deftest intersection ()
"`!intersection' returns a new list of only elements that are in both given lists." "`!intersection' returns a new list of only elements that are in both given lists."
(should (equal (!intersection '() '()) '())) (should (equal (!intersection '() '()) '()))
(should (equal (!intersection '(1 2 3) '(4 5 6)) '())) (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 () (ert-deftest uniq ()
"`!uniq' returns a new list of only unique elements." "`!uniq' returns a new list of only unique elements."
(should (equal (!uniq '()) '())) (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? () (ert-deftest contains? ()
"`!contains?' returns t if the list contains the element." "`!contains?' returns t if the list contains the element."
@ -78,4 +90,5 @@
(should (!contains? '(1 2 3) 2)) (should (!contains? '(1 2 3) 2))
(should (not (!contains? '() '()))) (should (not (!contains? '() '())))
(should (not (!contains? '() 1))) (should (not (!contains? '() 1)))
(should (not (!contains? '(1 2 4) 3)))) (should (not (!contains? '(1 2 4) 3)))
)

Loading…
Cancel
Save