Lisp:循环函数

Lisp: cycle through functions

我使用 elfeed 在 emacs 中阅读 RSS,并希望制作一个循环浏览不同视图的功能:新闻、9gag、非新闻等。

我试图通过快速研究编写一些代码。当我按下 ctrl + 右箭头键时,它会循环浏览视图。它工作正常,但我认为这不是最方便的代码。

我还想让它在按 ctrl + 向左箭头键时反向浏览视图。

;;cycle through views
(setq a 0)
(defun cycle-list-view ()
  (interactive)
  (if (= a 3)
    (progn
    (elfeed-read-9gag)
    (setq a 4)
    ))
  (if (= a 2)
    (progn
    (elfeed-read-nonnews)
    (setq a 3)      
    ))
  (if (= a 1)
    (progn
    (elfeed-read-news)
    (setq a 2)      
    ))
  (if (= a 0)
    (progn
    (elfeed-read-defaultnews)
    (setq a 1)      
    ))
  (if (= a 4)
    (progn
    (setq a 0)
    ))
  )

 (global-set-key (kbd "<C-right>") 'cycle-list-view)

您或许应该将数据放在一个单独的变量中,并让您的代码简单地在该结构中维护一个全局索引变量。大概是这样的:

(defvar elfeed-views-list "List of views for `elfeed-cycle-views'.
Each member should be a cons of a label string and a function.
`elfeed-views--index' is a pointer into this structure."
  (("default" . #'elfeed-read-defaultnews)
   ("news" . #'elfeed-read-news)
   ("nonnews" . #'elfeed-read-nonnews)
   ("9gag" . #'elfeed-read-9gag)))

添加新视图不再需要更改代码 -- 只需将另一个项目推到此列表的末尾。

现在所有循环函数需要做的就是递增或递减此结构中的索引,并在结束时注意换行:

;; The double dash in the name suggests a private variable
(defvar elfeed-views--index "Index into `elfeed-views-list' for `elfeed-cycle-views'.
Use that function to manipulate the index." 0)

(defun elfeed-cycle-views (&optional skip)
  "Update `elfeed-views--index' by adding SKIP (default 1) and
wrapping if necessary, then call the function at this index in
`elfeed-views-list'."
  (interactive)
  (setq elfeed-views--index (+ elfeed-views-index (or skip 1)))
   ;; BUG: skips with an absolute value bigger than 1 don't wrap properly.
  (if (>= elfeed-views--index (length elfeed-views-list))
    (setq elfeed-views--index 0)
   (when (< elfeed-views--index 0)
     (setq elfeed-views--index (1- (length elfeed-views-list))) ))
  (eval (cdr (nth elfeed-views--index elfeed-views-list))) )

现在可以通过将 -1 作为 SKIP 参数传递来简单地以其他方式循环。

(未经测试,但至少这应该会给你一些想法。)

除了文体之外,您想了解 cond 而不是可怕的 if 语句序列。您显然不得不强制对 您的代码 进行不直观的反向排序,因为您不知道此控制结构。

看来 tripleee 的回答适用于您提出的一般性问题(关于如何循环函数列表)。对于特定问题(通过 Elfeed 过滤器循环),我前一段时间为自己写了一个实现,并认为我会 post 在这里以防它对你有用。

如果没有看到您正在调用的代码(例如,elfeed-read-news),很难说这与您的实现有何不同,但我猜您不需要将所有视图都写下来作为单独的功能(除非您还希望命令直接跳转到特定视图)。

此外,我应该注意到我的实现使用了 dash 库,您可以在 MELPA 上找到它。

(defvar my/elfeed-favorite-filters
  '("@6-months-ago +unread"
    "+todo")
  "A list of commonly-used filters for Elfeed.
Use `my/elfeed-search-next-favorite-filter' to cycle through
these.")

(defun my/elfeed-search-next-favorite-filter (&optional verbose)
  "Apply the next filter in `my/elfeed-favorite-filters'.

If the current search filter is an element of
`my/elfeed-favorite-filters', apply the filter immediately
following that one in the list, looping back to the beginning if
necessary.

If the current search filter is not an element of
`my/elfeed-favorite-filters', apply the first filter in the
list.

If `my/elfeed-favorite-filters' is empty, just apply the default
filter.

Return the filter applied.  When called interactively or the optional
VERBOSE parameter is non-nil, also print a message informing the user
of the newly applied filter."
  (interactive "p")
  (let ((new-filter
         (my/successor-in-list my/elfeed-favorite-filters
                                elfeed-search-filter :cyclical)))
    (elfeed-search-set-filter new-filter)
    (when verbose (message "Filter applied: %s" elfeed-search-filter))
    elfeed-search-filter))

(defun my/successor-in-list (list elt &optional cycle)
  "Return the element in LIST following ELT.
If ELT is not an element of LIST, return nil.

If ELT is the last element in LIST, return (car LIST) if the
optional parameter CYCLE is non-nil; otherwise, return nil."
  (require 'dash)                       ; For `-drop-while'
  (let ((found  (-drop-while (lambda (x) (not (equal x elt)))
                             list)))
    (cond
     ((null found)                   nil)
     ((or (cdr found) (null cycle))  (cadr found))
     (:else                          (car list)))))

我不使用 "cycle backward" 命令,但很容易添加一个:

(defun my/elfeed-search-prev-favorite-filter (&optional verbose)
  "Apply the previous filter in `my/elfeed-favorite-filters'.

As `my/elfeed-search-next-favorite-filter', but cycle backwards."
  (interactive "p")
  (let ((my/elfeed-favorite-filters (reverse my/elfeed-favorite-filters)))
    (my/elfeed-search-next-favorite-filter verbose)))