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
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

@ -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.

@ -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)))
)

Loading…
Cancel
Save