tco 20160810.1712(in MELPA)
tail-call optimisation for Emacs lisp

概要

tco.el は、関数の末尾再帰を行うパッケージです。
末尾再帰というのは、関数呼び出しの自分自身を最後に再帰呼び出しする再帰パターンです。
関数型言語では定番の手法で、Schemeでは実装仕様で末尾再帰を要求してくる言語です。

それをループに置き換えることで末尾再帰の最適化が行えます。
最適化を行えばstack overflowを防げます。
defunの末尾再帰バージョンともいえる defun-tco マクロを使います。
これがあればSchemerら関数型言語ガチ勢もEmacs Lispを楽しめますね!!!

;; -*- lexical-binding: t -*-
(require 'tco)
(setq lexical-binding t)

;;; 1〜nの総和 (Σ(k=1..n) k)
(defun-tco sum (n &optional accum)
  "末尾再帰バージョン"
  (setq accum (or accum 0))
  (if (zerop n)
      accum
    (sum (1- n) (+ accum n))))
(defun sum0 (m &optional accum)
  "末尾再帰しないバージョン"
  (setq accum (or accum 0))
  (if (zerop m)
      accum
    (sum0 (1- m) (+ accum m))))

これをバイトコンパイルして計測したところ…

(sum 10)                                ; => 55
(sum0 10)                               ; => 55

(benchmark-run 1000 (sum 500))          ; => (1.208836634 7 0.7694204509994051)
(benchmark-run 1000 (sum0 500))         ; => (1.2184165589999998 7 0.7778593409998393)

速度は変わっていません。

大きな数を指定するとstack overflowになってしまいます。

(setq max-lisp-eval-depth 600)
(sum 300)                               ; => 45150
(sum0 300)                              ; => error
;; (error "Lisp nesting exceeds `max-lisp-eval-depth'")

追記:sum0がsumを呼ぶというドジを踏んでしまい変なことを書いてしまいました。
申し訳ありませんでした。御指摘感謝です。

インストール

パッケージシステムを初めて使う人は
以下の設定を ~/.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/")))

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

M-x package-install tco

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

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


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