Search code examples
refactoringcommon-lispprimitive

What is the most elegant way to slightly tweak what the member function does in Common Lisp?


The member function works like this with strings:

CL-USER> (member "a" '("a" "b" "c") :test #'equalp)
("a" "b" "c")
CL-USER> (member "a" '( "b" "c") :test #'equalp)
NIL

I would like to slightly change its behavior. When the element is not part of the list, I would like to have as a return the element itself and not nil. Moreover, if the element is indeed part of the list, I would like to have nil.

So, something like:

CL-USER> (tweaked-member "a" '("a" "b" "c") :test #'equalp)
NIL
CL-USER> (tweaked-member "a" '( "b" "c") :test #'equalp)
"a"

Thus, I did this naive approach:

(defun so-question (str str-list)
  (if (member str str-list :test #'string=)
      nil
      str))

Is there a more elegant way of doing this?

Maybe using member-if-not and :key?


Solution

  • I don't know if this is more elegant; your "naive" solution might be more clear, but you could do this:

    (defun tweaked-member (needle haystack)
      (and (not (member needle haystack :test #'equalp)) needle))
    
    CL-USER> (tweaked-member "a" '("a" "b" "c"))
    NIL
    CL-USER> (tweaked-member "a" '("b" "c"))
    "a"
    

    You could use find in place of member:

    (defun tweaked-member (needle haystack)
      (and (not (find needle haystack :test #'equalp)) needle))
    

    I didn't notice until now that my version does not take a :test argument, as you asked. But:

    (defun tweaked-member (needle haystack &rest args)
      (and (not (apply #'member needle haystack args)) needle))
    
    CL-USER> (tweaked-member "a" '("a" "b" "c") :test #'equalp)
    NIL
    CL-USER> (tweaked-member "a" '("b" "c") :test #'equalp)
    "a"