判断major-mode是否在list中时应该使用memq还是member?

我现在有如下为了防止irony-mode在php-mode中启用的代码:

(when (memq major-mode '(c-mode c++-mode objc-mode))
     (irony-mode 1))

我不明白这种情况下该用memq还是member…… 如果重点考虑性能,应该用哪个?

谢谢各位

DEFUN ("member", Fmember, Smember, 2, 2, 0,
       doc: /* Return non-nil if ELT is an element of LIST.  Comparison done with `equal'.
The value is actually the tail of LIST whose car is ELT.  */)
  (Lisp_Object elt, Lisp_Object list)
{
  Lisp_Object tail = list;
  FOR_EACH_TAIL (tail)
    if (! NILP (Fequal (elt, XCAR (tail))))
      return tail;
  CHECK_LIST_END (tail, list);
  return Qnil;
}

DEFUN ("memq", Fmemq, Smemq, 2, 2, 0,
       doc: /* Return non-nil if ELT is an element of LIST.  Comparison done with `eq'.
The value is actually the tail of LIST whose car is ELT.  */)
  (Lisp_Object elt, Lisp_Object list)
{
  Lisp_Object tail = list;
  FOR_EACH_TAIL (tail)
    if (EQ (XCAR (tail), elt))
      return tail;
  CHECK_LIST_END (tail, list);
  return Qnil;
}
# define EQ(x, y) lisp_h_EQ (x, y)

#define lisp_h_EQ(x, y) (XLI (x) == XLI (y))

# define XLI(o) lisp_h_XLI (o)

# define lisp_h_XLI(o) ((EMACS_INT) (o))
DEFUN ("equal", Fequal, Sequal, 2, 2, 0,
       doc: /* Return t if two Lisp objects have similar structure and contents.
They must have the same data type.
Conses are compared by comparing the cars and the cdrs.
Vectors and strings are compared element by element.
Numbers are compared via `eql', so integers do not equal floats.
\(Use `=' if you want integers and floats to be able to be equal.)
Symbols must match exactly.  */)
  (Lisp_Object o1, Lisp_Object o2)
{
  return internal_equal (o1, o2, EQUAL_PLAIN, 0, Qnil) ? Qt : Qnil;
}

/* Return true if O1 and O2 are equal.  EQUAL_KIND specifies what kind
   of equality test to use: if it is EQUAL_NO_QUIT, do not check for
   cycles or large arguments or quits; if EQUAL_PLAIN, do ordinary
   Lisp equality; and if EQUAL_INCLUDING_PROPERTIES, do
   equal-including-properties.
   If DEPTH is the current depth of recursion; signal an error if it
   gets too deep.  HT is a hash table used to detect cycles; if nil,
   it has not been allocated yet.  But ignore the last two arguments
   if EQUAL_KIND == EQUAL_NO_QUIT.  */

static bool
internal_equal (Lisp_Object o1, Lisp_Object o2, enum equal_kind equal_kind,
		int depth, Lisp_Object ht)
{
 tail_recurse:
  if (depth > 10)
    {
      eassert (equal_kind != EQUAL_NO_QUIT);
      if (depth > 200)
	error ("Stack overflow in equal");
      if (NILP (ht))
	ht = CALLN (Fmake_hash_table, QCtest, Qeq);
      switch (XTYPE (o1))
	{
	case Lisp_Cons: case Lisp_Vectorlike:
	  {
	    struct Lisp_Hash_Table *h = XHASH_TABLE (ht);
	    EMACS_UINT hash;
	    ptrdiff_t i = hash_lookup (h, o1, &hash);
	    if (i >= 0)
	      { /* `o1' was seen already.  */
		Lisp_Object o2s = HASH_VALUE (h, i);
		if (!NILP (Fmemq (o2, o2s)))
		  return true;
		else
		  set_hash_value_slot (h, i, Fcons (o2, o2s));
	      }
	    else
	      hash_put (h, o1, Fcons (o2, Qnil), hash);
	  }
	default: ;
	}
    }

  if (EQ (o1, o2))
    return true;
  if (XTYPE (o1) != XTYPE (o2))
    return false;

  switch (XTYPE (o1))
    {
    case Lisp_Float:
      return same_float (o1, o2);

    case Lisp_Cons:
      if (equal_kind == EQUAL_NO_QUIT)
	for (; CONSP (o1); o1 = XCDR (o1))
	  {
	    if (! CONSP (o2))
	      return false;
	    if (! equal_no_quit (XCAR (o1), XCAR (o2)))
	      return false;
	    o2 = XCDR (o2);
	    if (EQ (XCDR (o1), o2))
	      return true;
	  }
      else
	FOR_EACH_TAIL (o1)
	  {
	    if (! CONSP (o2))
	      return false;
	    if (! internal_equal (XCAR (o1), XCAR (o2),
				  equal_kind, depth + 1, ht))
	      return false;
	    o2 = XCDR (o2);
	    if (EQ (XCDR (o1), o2))
	      return true;
	  }
      depth++;
      goto tail_recurse;

    case Lisp_Vectorlike:
      {
	register int i;
	ptrdiff_t size = ASIZE (o1);
	/* Pseudovectors have the type encoded in the size field, so this test
	   actually checks that the objects have the same type as well as the
	   same size.  */
	if (ASIZE (o2) != size)
	  return false;
	if (BIGNUMP (o1))
	  return mpz_cmp (XBIGNUM (o1)->value, XBIGNUM (o2)->value) == 0;
	if (OVERLAYP (o1))
	  {
	    if (!internal_equal (OVERLAY_START (o1), OVERLAY_START (o2),
				 equal_kind, depth + 1, ht)
		|| !internal_equal (OVERLAY_END (o1), OVERLAY_END (o2),
				    equal_kind, depth + 1, ht))
	      return false;
	    o1 = XOVERLAY (o1)->plist;
	    o2 = XOVERLAY (o2)->plist;
	    depth++;
	    goto tail_recurse;
	  }
	if (MARKERP (o1))
	  {
	    return (XMARKER (o1)->buffer == XMARKER (o2)->buffer
		    && (XMARKER (o1)->buffer == 0
			|| XMARKER (o1)->bytepos == XMARKER (o2)->bytepos));
	  }
	/* Boolvectors are compared much like strings.  */
	if (BOOL_VECTOR_P (o1))
	  {
	    EMACS_INT size = bool_vector_size (o1);
	    if (size != bool_vector_size (o2))
	      return false;
	    if (memcmp (bool_vector_data (o1), bool_vector_data (o2),
			bool_vector_bytes (size)))
	      return false;
	    return true;
	  }
	if (WINDOW_CONFIGURATIONP (o1))
	  {
	    eassert (equal_kind != EQUAL_NO_QUIT);
	    return compare_window_configurations (o1, o2, false);
	  }

	/* Aside from them, only true vectors, char-tables, compiled
	   functions, and fonts (font-spec, font-entity, font-object)
	   are sensible to compare, so eliminate the others now.  */
	if (size & PSEUDOVECTOR_FLAG)
	  {
	    if (((size & PVEC_TYPE_MASK) >> PSEUDOVECTOR_AREA_BITS)
		< PVEC_COMPILED)
	      return false;
	    size &= PSEUDOVECTOR_SIZE_MASK;
	  }
	for (i = 0; i < size; i++)
	  {
	    Lisp_Object v1, v2;
	    v1 = AREF (o1, i);
	    v2 = AREF (o2, i);
	    if (!internal_equal (v1, v2, equal_kind, depth + 1, ht))
	      return false;
	  }
	return true;
      }
      break;

    case Lisp_String:
      if (SCHARS (o1) != SCHARS (o2))
	return false;
      if (SBYTES (o1) != SBYTES (o2))
	return false;
      if (memcmp (SDATA (o1), SDATA (o2), SBYTES (o1)))
	return false;
      if (equal_kind == EQUAL_INCLUDING_PROPERTIES
	  && !compare_string_intervals (o1, o2))
	return false;
      return true;

    default:
      break;
    }

  return false;
}

好像没区别

(benchmark 1000000 '(memq 'eshell-mode '(eshell-mode term-mode inf-ruby-mode))) ;; 0.076034s
(benchmark 1000000 '(member 'eshell-mode '(eshell-mode term-mode inf-ruby-mode))) ;; 0.078523s

存在1%的误差,可以忽略不计了

感觉这么测有毒的,测出来居然 string=equal 也是一样的


用下面这个, 不知为何我本地跑 benchmark 居然还能跑出一个负的时间,感觉很不靠谱

(benchmark-run 1000000 (memq 'eshell-mode '(eshell-mode term-mode inf-ruby-mode)))
(benchmark-run 1000000 (member 'eshell-mode '(eshell-mode term-mode inf-ruby-mode)))

要看情况,如果是符号列表,理论上是 memq 性能好一点。

主要区别在一个用 eq(memq),一个用 equal(member):

(member 'foo '(foo bar quux))           ; => non-nil
(member "foo" '("foo" "bar" "quux"))    ; => non-nil
(member 111 '(111 222 333))             ; => non-nil

(memq   'foo '(foo bar quux))           ; => non-nil
(memq   "foo" '("foo" "bar" "quux"))    ; => nil
(memq   111 '(111 222 333))             ; => non-nil

类似还有 assoc & assq

我觉得 eq/eql/equal 这些函数命名很糟糕,宁愿用更具体的函数,比如 string=

使用 eq 比较 Symbol,没有必要用 equal。类似的,一般用 = 比较数字,而不是 equal 等。