loop 20160813.707(in MELPA)
friendly imperative loop structures

概要

Emacsの素のループ制御構造は単体ではループを中断する機能が用意されていません。
たいていの言語には組み込みで用意されていますが、
Emacs Lispで実現するためにはcatchで囲む必要があって面倒です。
そこで loop.el はbreak/continue/returnが使えるループ制御構造を用意します。

標準で用意されているCommon Lispのloopマクロ cl-loop を使えば書けるものですが、
loopマクロに苦手意識を持つならば使ってみてはいかがでしょうか。
慣れ親しんだ感覚でループが記述できるのは安心感があります。

各定義は簡単なマクロになっているので、ソースコードを読んでみると勉強になります。

インストール

パッケージシステムを初めて使う人は
以下の設定を ~/.emacs.d/init.el の
先頭に加えてください。

(package-initialize)
(setq package-archives
      '(("gnu" . "http://elpa.gnu.org/packages/")
        ("melpa" . "http://melpa.org/packages/")
        ("org" . "http://orgmode.org/elpa/")))

初めてloopを使う方は
以下のコマンドを実行します。

M-x package-install loop

アップグレードする方は、
以下のコマンドでアップグレードしてください。
そのためにはpackage-utilsパッケージが必要です。

M-x package-install package-utils (初めてアップグレードする場合のみ)
M-x package-utils-upgrade-by-name loop

使用例 160825054100.loop.el(以下のコードと同一)

;;; 0〜9の総和
(let ((x 0)
      (sum 0))
  (loop-while (< x 10)
    (setq sum (+ sum x))
    (setq x (1+ x)))
  sum)                                ; => 45
(cl-loop with x = 0
         with sum = 0
         while (< x 10)
         do
         (setq sum (+ sum x))
         (setq x (1+ x))
         finally return sum)          ; => 45
(cl-loop for x from 0 below 10 sum x) ; => 45

;;; 0〜5の総和
(let ((x 0)
        (sum 0))
    (loop-while (< x 10)
      (setq sum (+ sum x))
      (setq x (1+ x))
      (when (= x 6)
        (loop-break)))
    sum)                                ; => 15
(cl-loop with x = 0
         with sum = 0
         while (< x 10)
         do
         (setq sum (+ sum x))
         (setq x (1+ x))
         when (= x 6)
         return sum)                    ; => 15
(cl-loop with sum = 0
         for x from 0 below 10
         when (= x 6) return sum
         do (setq sum (+ sum x)))       ; => 15
;;; 1+3+4+5
(let ((x 0)
      (sum 0))
  (loop-while (< x 5)
    (setq x (1+ x))
    (when (= x 2)
      (loop-continue))
    (setq sum (+ sum x)))
  sum)                                  ; => 13
(cl-loop with x = 0
         with sum = 0
         while (< x 5)
         do
         (setq x (1+ x))
         if (= x 2)
           do (ignore)
         else
           do (setq sum (+ sum x))
         finally return sum)                    ; => 13
;;; do-while
(let ((x 0)
      (sum 0))
    ;; our condition is false on the first iteration
    (loop-do-while (and (> x 0) (< x 10))
      (setq sum (+ sum x))
      (setq x (1+ x)))
    sum)                                ; => 45
(cl-loop with x = 0
         with sum = 0
         do
         (setq sum (+ sum x))
         (setq x (1+ x))
         while (and (> x 0) (< x 10))
         finally return sum)            ; => 45
;;; until
(let ((x 0)
        (sum 0))
    ;; sum the numbers 0 to 9
    (loop-until (= x 10)
      (setq sum (+ sum x))
      (setq x (1+ x)))
    sum)                                ; => 45
(cl-loop with x = 0
         with sum = 0
         until (= x 10)
         do
         (setq sum (+ sum x))
         (setq x (1+ x))
         finally return sum)            ; => 45
;;; foreach
(let ((sum 0))
    ;; sum the numbers 1 to 5
    (loop-for-each x (list 1 2 3 4 5 6 7 8 9)
      (setq sum (+ sum x))
      (when (= x 5)
        (loop-break)))
    sum)                                ; => 15
(cl-loop with sum = 0
         for x in (list 1 2 3 4 5 6 7 8 9)
         do
         (setq sum (+ sum x))
         when (= x 5) return sum)       ; => 15
;;; for-each-line (バッファ各行についてループする)
(with-temp-buffer
  (insert "foo\nbar\nbaz")
  (let ((lines nil))
    (loop-for-each-line
      (let ((line-start (progn (beginning-of-line) (point)))
            (line-end (progn (end-of-line) (point))))
        (push (buffer-substring line-start line-end) lines)
        ;; This line should have no effect.
        (forward-line 1)))
    (nreverse lines)))                ; => ("foo" "bar" "baz")

(with-temp-buffer
  (cl-loop with lines
           with line-start
           with line-end
           initially
           (insert "foo\nbar\nbaz")
           (goto-char (point-min))
           do
           (save-excursion
             (setq line-start (progn (beginning-of-line) (point)))
             (setq line-end (progn (end-of-line) (point)))
             (push (buffer-substring line-start line-end) lines)
             (forward-line 1))
           (forward-line 1)
           until (eobp)
           finally return (nreverse lines)))     ; => ("foo" "bar" "baz")
;;; for-each-line は it で各行の内容を参照できる
(with-temp-buffer
  (insert "foo\nbar\nbaz")
  (let ((lines nil))
    (loop-for-each-line
      (push it lines))
    (nreverse lines)))                  ; => ("foo" "bar" "baz")

実行方法

$ wget http://rubikitch.com/f/160825054100.loop.el
$ emacs -Q -f package-initialize -l 160825054100.loop.el


本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。