The Good Thing
The code works and does what was intended. Making it shorter and improving readability are improvements not bug fixes.
Readability
It is hard to follow the logic of the code. Adding a documentation string and comments would assist a future programmer (which might be the program's author in a few months) in figuring out what is going on and guide the programmer in making changes.
Names The name modulename-vinlist
does not follow traditional naming conventions for Lisp programs. Hyphen case is more typical module-name-v-in-list
would be more conventional. I'd probably favor adding a few extra keystrokes for legibility with module-name-value-in-list
because it is more readable and often spend longer reading code than writing it.
Philosophy
As noted in the accepted answer Emacs Lisp provides the member
function as a simple way of determining if a value is in a list. However, it is possible to mean different things by 'value' depending on context. Emacs Lisp addresses this with additional functions such as memq
, memql
etc. to provide more stringent set semantics for lists. Emacs Lisp also provides a more general member-ignore case
for contexts where strings matter.
For example working with string values:
ELISP> (member "PRINTED VALUE" '("unprinted value" "printed value"))
nil
ELISP> (member-ignore-case "PRINTED VALUE" '("unprinted value" "printed value"))
("printed value")
Or based on strict identity with memq
:
ELSIP> (let ((a 'b))
(memq a '(c b d)))
(b d)
But there is not a function for working with numerical values of different types:
ELISP> (member '2.0 '(1 2 3))
nil
To address this last class of cases means going even further, and by 'further' I mean looking outside of Emacs Lisp. ANSI Common Lisp implements it's member
function to take an arbitrary test for equality and this is the sort of thing that might be useful.
And since we're already writing a function...how hard could it be to add that flexibility?
An Implementation of value-in-list
Like most questions that start with "How hard could it be" it turns out that it is not as easy as was initially thought. In this case because there appears to be a 'bug' like behavior as documented in the comments.
(defun value-in-list (value list &optional test)
"Uses test to determine if value is in list.
If value is in in list, returns the cdr of the list
which begins with value: the return value mimics the behavior of MEMBER.
Example: (my-member 3.0 '(1 2 3) '=) -> (3)
(my-member '(1) '((1) (2) (3)) 'eq) -> nil
Remarks: This function could in theory be structured as the local function
'my-member, but Emacs-Lisp runs out of stack space if 'eq or 'eql are passed
to 'my-member within another function. Seems like a bug."
(let
((my-member (lambda (value list test)
(if (funcall test value (car list))
list
(my-member value (cdr list) test)))))
(cond
((null test)
(member value list))
((eq test 'eq)
(memq value list))
((eq test 'eql)
(memql value list))
(t (funcall my-member value list test)))))
Highlights
Note the use funcall
in two places where a symbol bound to a function is passed as a parameter to another function:
- Within the locally defined function
my-member
.
- In the call to
my-member
in the t
condition of the cond
expression.
This is necessary within Emacs Lisp and one of the ways it varies from Common Lisp and various Schemes.
Example Use Despite the difficulties that add complexity, value-in-list
provides a great deal of flexibility:
ELISP> (value-in-list 2 '(1 2 3))
(2 3)
ELISP> (value-in-list '(2) '(1 (2) 3))
((2)
3)
ELISP> (value-in-list 1 '(1 2 3) #'(lambda (x y) (< x y)))
(2 3)
ELISP> (value-in-list 2.0 '(1 2 3) '=)
(2 3)
ELISP> (value-in-list 2
'(1 (2) 3)
#'(lambda (x y) (if (consp y)
(= x (car y)))))
((2)
3)
Final Thoughts
Shorter is so long as it does not come at the cost of power. Writing one's own version of a function provides an opportunity to make a pneumatic tire along with the risk of reinventing the wheel.