[インデックス 17856] ファイルの概要
このコミットは、Go言語のEmacsメジャーモードであるgo-mode.el
に対する変更を取り消すものです。具体的には、以前のコミット(CL 19010044 / dbcd720e5396)で導入された、Emacs Lispの関数シンボルをクォートする際に#'
を使用する変更を元に戻し、代わりに'
を使用するように修正しています。この変更は、特定のEmacsバージョン(GNU Emacs 22.1.1)でgo-mode
が正しく動作しなくなる問題に対処するために行われました。
コミット
- コミットハッシュ:
75d5c4af7128e8fd6e2df71f631925715784de46
- Author: Alan Donovan adonovan@google.com
- Date: Tue Oct 29 22:17:14 2013 -0400
- コミットメッセージ:
undo CL 19010044 / dbcd720e5396 bradfitz reports: > It breaks go-mode on GNU Emacs 22.1.1 as ships with OS X 10.8.6. ««« original CL description misc/emacs: various cleanups - Use #' for function symbols - Remove unused variables - Use declare-function to shut up byte compiler R=adonovan CC=golang-dev https://golang.org/cl/19010044 »»» R=bradfitz CC=golang-dev https://golang.org/cl/19640043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/75d5c4af7128e8fd6e2df71f631925715784de46
元コミット内容
このコミットが取り消した元のコミット(CL 19010044 / dbcd720e5396)は、misc/emacs/go-mode.el
に対して以下のクリーンアップを行っていました。
- 関数シンボルに
#'
を使用する。 - 未使用の変数を削除する。
- バイトコンパイラの警告を抑制するために
declare-function
を使用する。
この元のコミットの意図は、Emacs Lispコードの現代的な慣習に合わせ、より堅牢で警告の少ないコードベースにすることでした。特に#'
の使用は、シンボルが関数として評価されることを明示し、意図しない変数参照を防ぐための推奨されるプラクティスです。
変更の背景
このコミットの背景には、元のコミット(CL 19010044)が引き起こした互換性の問題があります。具体的には、bradfitz
氏からの報告により、元の変更が「OS X 10.8.6に同梱されているGNU Emacs 22.1.1でgo-mode
を壊す」ことが判明しました。
Emacs Lispでは、シンボルをクォートする方法として'
(quote)と#'
(function)の2つが主要です。#'
は、シンボルが関数として評価されることを明示的に示すために使用されます。これは、Emacs Lispの進化とともに推奨されるようになったプラクティスですが、古いEmacsバージョンでは#'
の挙動が異なるか、あるいは特定のコンテキストで予期せぬ問題を引き起こす可能性がありました。
GNU Emacs 22.1.1は2007年にリリースされたバージョンであり、このコミットが行われた2013年時点ではすでに古い部類に入ります。しかし、OSの標準インストールに含まれるなど、依然として多くのユーザーが使用している可能性がありました。そのため、広範なユーザーベースでの互換性を確保するため、この問題を引き起こす変更を元に戻すことが決定されました。
前提知識の解説
Emacs Lispのシンボルと関数
Emacs Lispにおいて、シンボルはプログラムの基本的な構成要素です。変数名、関数名、マクロ名など、様々なものを表すことができます。シンボルは、その名前によって一意に識別されます。
Emacs Lispでは、シンボルはデフォルトで変数として評価されます。例えば、foo
というシンボルが評価されると、Emacsはfoo
という名前の変数の値を探します。
クォーティング ('
と #
') の違いと用途
シンボルを評価せずに、そのシンボル自体として扱いたい場合、クォーティングを使用します。Emacs Lispには主に2つのクォーティングメカニズムがあります。
-
'
(quote): これは最も一般的なクォーティング形式で、シンボルやリストを評価せずにそのままの形で扱いたい場合に使用します。例えば、'foo
はシンボルfoo
自体を表し、'(1 2 3)
はリスト(1 2 3)
自体を表します。'
は、シンボルが変数として評価されるのを防ぎ、そのシンボル自体をデータとして扱いたい場合に用いられます。 -
#'
(function): これは、シンボルが関数として評価されることを明示的に示すために使用されます。#'foo
は、シンボルfoo
が指す関数オブジェクトを返します。これは、funcall
やapply
などの関数を呼び出す際に、引数として関数を渡す場合によく使われます。#'
は、シンボルが変数としてではなく、関数として扱われることを意図していることをコードの読み手に明確に伝えます。また、バイトコンパイラがシンボルを関数として認識し、関連する警告を抑制するのに役立つことがあります。
違いの要点:
'symbol
: シンボルsymbol
自体をデータとして扱う。#'symbol
: シンボルsymbol
が指す関数オブジェクトをデータとして扱う。
現代のEmacs Lispプログラミングでは、関数を引数として渡す場合や、関数として使用されるシンボルを明示する場合には#'
の使用が推奨されます。しかし、古いEmacsバージョンでは#'
のサポートが不完全であったり、特定のコンテキストで問題を引き起こすことがありました。
defalias
と define-key
の役割
-
defalias
: このマクロは、既存の関数やコマンドに別名(エイリアス)を定義するために使用されます。例えば、(defalias 'new-name 'old-name)
とすると、new-name
はold-name
と同じ関数を指すようになります。このコミットでは、kill-whole-line
やposition-bytes
といった組み込み関数にエイリアスを定義するために使用されています。 -
define-key
: この関数は、キーマップにキーバインディングを定義するために使用されます。キーマップは、特定のモード(例:go-mode-map
)でどのキーシーケンスがどのコマンド(関数)を実行するかを定義するデータ構造です。例えば、(define-key m "}" 'go-mode-insert-and-indent)
は、キーマップm
において}
キーが押されたときにgo-mode-insert-and-indent
関数を実行するように設定します。
declare-function
の目的
declare-function
は、Emacs Lispのバイトコンパイラに対して、特定の関数が別のファイルで定義されていることを宣言するために使用されるマクロです。これにより、バイトコンパイラは、その関数が未定義であるという警告("undefined function")を発することを抑制できます。
元のコミットでは、go--position-bytes
がgo-mode.el
内で定義される前に使用されていたため、バイトコンパイラの警告を避けるためにdeclare-function
が追加されていました。しかし、このコミットではgo--position-bytes
の定義がdeclare-function
の直後に移動されたため、declare-function
は不要となり削除されています。
技術的詳細
このコミットの技術的詳細は、Emacs Lispのシンボル評価とバイトコンパイルの挙動、特に古いEmacsバージョンでの#'
の扱いに集約されます。
Emacs Lispでは、コードがロードされる際に評価されます。シンボルが評価されると、Emacsはまずそのシンボルが変数として定義されているかを探し、次にそれが関数として定義されているかを探します。
-
'symbol
の場合:'symbol
は、評価時にシンボルsymbol
そのものを返します。これは、シンボルをデータとして扱いたい場合に安全な方法です。define-key
やdefalias
のようなマクロは、引数としてシンボル自体を期待することが多く、そのシンボルが指す関数を後で解決します。 -
#'symbol
の場合:#'symbol
は、評価時にシンボルsymbol
が指す関数オブジェクトを返します。これは、シンボルが確実に「関数」として扱われることを保証します。現代のEmacs Lispでは推奨されるプラクティスですが、古いEmacsバージョン(特にEmacs 22.1.1のような古い安定版)では、#'
の内部実装や、それが返す関数オブジェクトの扱いに関して、互換性の問題があった可能性があります。
考えられる問題点としては、以下の点が挙げられます。
#'
の構文解析または評価のバグ: Emacs 22.1.1のLispインタープリタが#'
構文を正しく解析できない、またはその結果として返される関数オブジェクトを特定のコンテキスト(例:define-key
の内部処理)で正しく扱えないバグがあった可能性があります。- バイトコンパイル時の問題:
go-mode.el
は通常、バイトコンパイルされて.elc
ファイルとして配布されます。バイトコンパイラが#'
を含むコードをコンパイルする際に、Emacs 22.1.1のランタイム環境と互換性のないバイトコードを生成した可能性も考えられます。これにより、ロード時にエラーが発生したり、予期せぬ挙動を引き起こしたりしたかもしれません。 - 動的スコープとレキシカルスコープの混同: Emacs Lispは歴史的に動的スコープを使用してきましたが、最近のバージョンではレキシカルスコープもサポートしています。
#'
はレキシカルスコープの文脈でより意味を持つことが多いですが、Emacs 22.1.1のような古いバージョンでは、動的スコープの挙動と#'
が組み合わさることで、予期せぬ名前解決の問題が発生した可能性も否定できません。
このコミットでは、これらの潜在的な問題を回避するため、安全策として#'
の使用を'
に戻しています。これにより、Emacs 22.1.1のような古い環境でもgo-mode
が安定して動作するようになります。
また、declare-function go--position-bytes "go-mode" (point)
の削除も注目すべき点です。元のコミットでは、go--position-bytes
が定義される前に使用されていたため、バイトコンパイラが「未定義関数」の警告を出すのを防ぐためにこの宣言が追加されました。しかし、このコミットでは、go--position-bytes
の定義がその使用箇所よりも前に移動されたため、declare-function
は不要となり削除されました。これはコードの整理と、不要な宣言の削除によるクリーンアップの一環です。
コアとなるコードの変更箇所
misc/emacs/go-mode.el
ファイルにおける変更は以下の通りです。
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -41,8 +41,8 @@
(defalias 'go--kill-whole-line
(if (fboundp 'kill-whole-line)
- #'kill-whole-line
- #'kill-entire-line))
+ 'kill-whole-line
+ 'kill-entire-line))
;; Delete the current line without putting it in the kill-ring.
(defun go--delete-whole-line (&optional arg)
@@ -57,12 +57,11 @@
(go--kill-whole-line arg)))\n
-(declare-function go--position-bytes "go-mode" (point))
;; XEmacs unfortunately does not offer position-bytes. We can fall
;; back to just using (point), but it will be incorrect as soon as
;; multibyte characters are being used.\n
(if (fboundp 'position-bytes)
- (defalias 'go--position-bytes #'position-bytes)
+ (defalias 'go--position-bytes 'position-bytes)
(defun go--position-bytes (point) point))\n
(defun go--old-completion-list-style (list)\n
@@ -274,15 +273,15 @@ For mode=set, all covered lines will have this weight.\"\n
(defvar go-mode-map
(let ((m (make-sparse-keymap)))\n- (define-key m "}" #'go-mode-insert-and-indent)\n- (define-key m ")" #'go-mode-insert-and-indent)\n- (define-key m "," #'go-mode-insert-and-indent)\n- (define-key m ":" #'go-mode-insert-and-indent)\n- (define-key m "=" #'go-mode-insert-and-indent)\n- (define-key m (kbd "C-c C-a") #'go-import-add)\n- (define-key m (kbd "C-c C-j") #'godef-jump)\n- (define-key m (kbd "C-x 4 C-c C-j") #'godef-jump-other-window)\n- (define-key m (kbd "C-c C-d") #'godef-describe)\n+ (define-key m "}" 'go-mode-insert-and-indent)\n+ (define-key m ")" 'go-mode-insert-and-indent)\n+ (define-key m "," 'go-mode-insert-and-indent)\n+ (define-key m ":" 'go-mode-insert-and-indent)\n+ (define-key m "=" 'go-mode-insert-and-indent)\n+ (define-key m (kbd "C-c C-a") 'go-import-add)\n+ (define-key m (kbd "C-c C-j") 'godef-jump)\n+ (define-key m (kbd "C-x 4 C-c C-j") 'godef-jump-other-window)\n+ (define-key m (kbd "C-c C-d") 'godef-describe)\n m)\n "Keymap used by Go mode to implement electric keys.")\n
@@ -391,7 +390,7 @@ current line will be returned.\"\n
(defun go-indentation-at-point ()\n (save-excursion\n- (let (start-nesting)\n+ (let (start-nesting (outindent 0))\n (back-to-indentation)\n (setq start-nesting (go-paren-level))\n
@@ -421,6 +420,7 @@ current line will be returned.\"\n (interactive)\n (let (indent\n shift-amt\n+ end\n (pos (- (point-max) (point)))\n (point (point))\n (beg (line-beginning-position)))\n@@ -511,7 +511,7 @@ consider binding godef-jump to `M-.\', which is the default key\n for `find-tag\':\n
\\(add-hook 'go-mode-hook (lambda ()\n- (local-set-key (kbd \"M-.\") #'godef-jump)))\n+ (local-set-key (kbd \"M-.\") 'godef-jump)))\n
Please note that godef is an external dependency. You can install\n it with\n@@ -531,7 +531,7 @@ recommended that you look at goflymake\n '(go--build-font-lock-keywords))\n
;; Indentation\n- (set (make-local-variable 'indent-line-function) #'go-mode-indent-line)\n+ (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)\n
;; Comments\n (set (make-local-variable 'comment-start) "// ")\n@@ -539,12 +539,12 @@ recommended that you look at goflymake\n (set (make-local-variable 'comment-use-syntax) t)\n (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")\n
- (set (make-local-variable 'beginning-of-defun-function) #'go-beginning-of-defun)\n- (set (make-local-variable 'end-of-defun-function) #'go-end-of-defun)\n+ (set (make-local-variable 'beginning-of-defun-function) 'go-beginning-of-defun)\n+ (set (make-local-variable 'end-of-defun-function) 'go-end-of-defun)\n
(set (make-local-variable 'parse-sexp-lookup-properties) t)\n (if (boundp 'syntax-propertize-function)\n- (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax))\n+ (set (make-local-variable 'syntax-propertize-function) 'go-propertize-syntax))\n
(set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))\n (add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make-hash-table :test 'eql))) t t)\n@@ -898,13 +898,13 @@ If IGNORE-CASE is non-nil, the comparison is case-insensitive.\"\n (mapcar (lambda (file)\n (let ((sub (substring file (length pkgdir) -2)))\n (unless (or (go--string-prefix-p "obj/" sub) (go--string-prefix-p "tool/" sub))\n- (mapconcat #'identity (cdr (split-string sub "/")) "/"))))\n+ (mapconcat 'identity (cdr (split-string sub "/")) "/"))))\n (if (file-directory-p dir)\n (directory-files dir t "\\.a$"))))\n (if (file-directory-p pkgdir)\n (go--directory-dirs pkgdir)))))\n (go-root-and-paths)))\n- #'string<))\n+ 'string<))\n
(defun go-unused-imports-lines ()\n ;; FIXME Technically, -o /dev/null fails in quite some cases (on\n@@ -994,7 +994,7 @@ description at POINT.\"\n (let ((description (cdr (butlast (godef--call point) 1))))\n (if (not description)\n (message "No description found for expression at point")\n- (message "%s" (mapconcat #'identity description "\\n"))))\n+ (message "%s" (mapconcat 'identity description "\\n"))))\n (file-error (message "Could not run godef binary"))))\n
(defun godef-jump (point &optional other-window)\n@@ -1145,6 +1145,6 @@ for.\"\n (go--coverage-make-overlay range (cadr ranges-and-divisor))))\n
(if (not (eq cur-buffer (current-buffer)))\n- (display-buffer (current-buffer) #'display-buffer-reuse-window)))))\n+ (display-buffer (current-buffer) 'display-buffer-reuse-window)))))\n
(provide 'go-mode)\n```
## コアとなるコードの解説
このコミットの主要な変更は、`misc/emacs/go-mode.el`ファイル内で、関数シンボルをクォートする際に使用されていた`#'`を`'`に置き換えることです。
1. **`defalias`の変更**:
```diff
- #'kill-whole-line
- #'kill-entire-line))
+ 'kill-whole-line
+ 'kill-entire-line))
```
`go--kill-whole-line`というエイリアスを定義する際に、`kill-whole-line`と`kill-entire-line`という組み込み関数を`#'`でクォートしていた箇所が`'`に変更されました。これは、これらのシンボルが関数として評価されることを明示する代わりに、単にシンボル自体を`defalias`に渡す形に戻したものです。`defalias`は引数としてシンボルを期待するため、`'`で十分であり、古いEmacsバージョンでの互換性を確保します。
2. **`declare-function`の削除と`defalias`の変更**:
```diff
-(declare-function go--position-bytes "go-mode" (point))
...
- (defalias 'go--position-bytes #'position-bytes)
+ (defalias 'go--position-bytes 'position-bytes)
```
`go--position-bytes`の定義が`declare-function`の直後に移動されたため、バイトコンパイラへの事前宣言が不要となり、`declare-function`の行が削除されました。また、`position-bytes`のエイリアス定義も`#'`から`'`に変更されています。
3. **`define-key`の変更**:
```diff
- (define-key m "}" #'go-mode-insert-and-indent)
...
- (define-key m (kbd "C-c C-d") #'godef-describe)
+ (define-key m "}" 'go-mode-insert-and-indent)
...
+ (define-key m (kbd "C-c C-d") 'godef-describe)
```
`go-mode-map`というキーマップにキーバインディングを定義する際に、各コマンド(関数)を`#'`でクォートしていた箇所が`'`に変更されました。`define-key`は、キーが押されたときに実行されるコマンドのシンボルを引数として受け取るため、ここでも`'`で十分です。この変更も、古いEmacsバージョンでの互換性問題を回避するためのものです。
4. **`local-set-key`の変更**:
```diff
- (local-set-key (kbd "M-.") #'godef-jump)))
+ (local-set-key (kbd "M-.") 'godef-jump)))
```
`go-mode-hook`にフックを追加する際に、`local-set-key`で`godef-jump`をバインドする箇所も`#'`から`'`に変更されています。
5. **`set`と`make-local-variable`の組み合わせの変更**:
```diff
- (set (make-local-variable 'indent-line-function) #'go-mode-indent-line)
...
- (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax))
+ (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
...
+ (set (make-local-variable 'syntax-propertize-function) 'go-propertize-syntax))
```
`indent-line-function`や`beginning-of-defun-function`などの変数にローカルな値を設定する際に、関数シンボルを`#'`でクォートしていた箇所が`'`に変更されました。これらの変数は関数オブジェクトを期待しますが、`set`関数はシンボルを評価してその値(この場合は関数オブジェクト)を代入するため、`'`でシンボルを渡すことで十分です。
6. **`mapconcat`の変更**:
```diff
- (mapconcat #'identity (cdr (split-string sub "/")) "/"))))
+ (mapconcat 'identity (cdr (split-string sub "/")) "/"))))
```
`mapconcat`関数は、リストの各要素に関数を適用し、その結果を文字列として連結するものです。ここで`identity`関数を`#'`で渡していた箇所が`'`に変更されました。`mapconcat`も関数オブジェクトを引数として受け取りますが、`'`で渡されたシンボルを評価して関数として使用できます。
これらの変更はすべて、`#'`の使用を`'`に戻すという一貫したパターンに従っています。これは、Emacs 22.1.1のような古いEmacsバージョンとの互換性を回復するための、実用的な解決策です。`#'`はより現代的なEmacs Lispの慣習ですが、特定の環境での問題発生時には、より広範な互換性を持つ`'`に戻すことが選択されることがあります。
## 関連リンク
- Go言語のEmacsモード (`go-mode.el`) のリポジトリ: [https://github.com/dominikh/go-mode.el](https://github.com/dominikh/go-mode.el) (Goプロジェクトの`misc/emacs`ディレクトリは、このリポジトリのミラーまたは派生である可能性があります)
- Emacs Lisp Manual: [https://www.gnu.org/software/emacs/manual/html_node/elisp/](https://www.gnu.org/software/emacs/manual/html_node/elisp/) (特に、"Quoting of Forms" や "Function Cells" の章が関連します)
## 参考にした情報源リンク
- Emacs Lisp Manual (GNU Emacs 22.1.1): [https://www.gnu.org/software/emacs/manual/elisp-22.1/elisp.html](https://www.gnu.org/software/emacs/manual/elisp-22.1/elisp.html) (古いバージョンのマニュアルを参照することで、当時の`#'`の挙動や推奨事項を理解するのに役立ちます)
- Stack OverflowやEmacs関連のフォーラムでの`'`と`#'`に関する議論。
- Go言語のコードレビューシステム (Gerrit) のCL (Change-list) ページ: [https://golang.org/cl/19010044](https://golang.org/cl/19010044) および [https://golang.org/cl/19640043](https://golang.org/cl/19640043) (元の変更とこの変更の経緯を直接確認できます)