Hatena::ブログ(Diary)

わからん

2011.12.17

[][] Haskell 用の emacs カスタマイズ例

Haskell Advent Calendar 2011 への参加記事です。ふだん emacs でコードを書いているプログラマが Haskell を使ってみようと思ったときに、10 分でそれなりの環境構築するための、便利な手順書となることを目指して書きました。~/.emacs や ~/.emacs.d/init.el などの設定ファイルは、dot.emacs と表記しています。


 Haskell の環境構築

Haskell 自体の環境構築の説明はこの記事の範囲外ですが、前提ですので、信頼できそうな説明へのリンクを載せておきます。

ただし、私は Ubuntu ユーザですが、トラビスさんとは異なり、ソースからではなく sudo apt-get install haskell-platform で済ませています。それから、以降の内容が Windows 環境でもうまくいくのか、よくわかっていません。


 haskell-mode のインストール

まずは haskell-mode をインストールしましょう。2011/12 の時点で、最新版は 2.8.0 です。

解凍してできた haskell-mode-2.8.0 というディレクトリを ~/.emacs.d/elisp/haskell-mode-2.8.0 に配置し、dot.emacs に次のように記載します。

(add-to-list 'load-path "~/.emacs.d/elisp/haskell-mode-2.8.0")

(require 'haskell-mode)
(require 'haskell-cabal)

 拡張子とメジャーモードの関連付け

dot.emacs に次の内容を追記して下さい。

(add-to-list 'auto-mode-alist '("\\.hs$" . haskell-mode))
(add-to-list 'auto-mode-alist '("\\.lhs$" . literate-haskell-mode))
(add-to-list 'auto-mode-alist '("\\.cabal\\'" . haskell-cabal-mode))

拡張子が ".hs" のファイルは haskell-mode で開きます。拡張子が ".lhs" のファイルは literate-haskell-mode で開きます。literate-haskell-mode は、先ほどインストールした、haskell-mode.el の中で定義されています。拡張子が ".lhs" のファイルは Literate Haskell のソースコードです。 Literate Haskell は、まるで自然言語の文章が主、ソースコードが従であるかのような見かけをした、Haskell のプログラムです。コードの行頭には ">" が付いています。私はたとえば、書籍「プログラミング Hasklell」を教材にした「スタート Haskell」という講習の課題でこのフォーマットを見かけました。literate-haskell-mode で開くことでコード部がシンタックスハイライトされます。後述する flymake も効いています(20行目の左端)。


f:id:kitokitoki:20111205003615p:image


拡張子が ".cabal" のファイルは、Haskell のパッケージ管理システム用の設定ファイルです。haskell-cabal-mode で開くようにしました。 このメジャーモードは、先ほどインストールした、haskell-cabal.el の中で定義されています(~/.emacs.d/elisp/haskell-mode-2.8.0 の下にあります)。たんなる色づけです。


 Haskell 製のコマンドと haskell-mode の関連付け

(add-to-list 'interpreter-mode-alist '("runghc" . haskell-mode))     ;#!/usr/bin/env runghc 用
(add-to-list 'interpreter-mode-alist '("runhaskell" . haskell-mode)) ;#!/usr/bin/env runhaskell 用

この 2 行の設定で、1 行目にシェバンがあり、拡張子のないコマンドのファイルも、Haskell モードで開くことができます。

#!/usr/bin/env runhaskell

main = putStrLn "Hello, World!"

cabal でインストールしたコンソールアプリのコードを開いたときの小さなストレスがなくなります。


 ghc-mod の導入

次に ghc-mod をインストールします。

cabal update
cabal install cabal-install

cabal install ghc-mod

dot.emacs に次のように書きます。

;; ghc-mod
;; cabal でインストールしたライブラリのコマンドが格納されている bin ディレクトリへのパスを exec-path に追加する
(add-to-list 'exec-path (concat (getenv "HOME") "/.cabal/bin"))
;; ghc-flymake.el などがあるディレクトリ ghc-mod を ~/.emacs.d 以下で管理することにした
(add-to-list 'load-path "~/.emacs.d/elisp/ghc-mod") 

(autoload 'ghc-init "ghc" nil t)

(add-hook 'haskell-mode-hook
  (lambda () (ghc-init)))

ghc-mod で何ができるかは、公式ページに記載されていますので、そちらを参照して下さい。

ghc-mod の機能の一つに、ghc-browse-document() という、インストール済みのパッケージのマニュアルをブラウザで開くコマンドがあります。C-M-d に割り当てられています。これの anything 版を作成しました。ここではそれを紹介します。次のコードを dot.emacs に貼りつけて下さい。anything.el 自体の説明や導入方法の解説は省略します。

(require 'anything)
(require 'anything-config)
(require 'anything-match-plugin)

(defvar anything-c-source-ghc-mod
  '((name . "ghc-browse-document")
    (init . anything-c-source-ghc-mod)
    (candidates-in-buffer)
    (candidate-number-limit . 9999999)
    (action ("Open" . anything-c-source-ghc-mod-action))))

(defun anything-c-source-ghc-mod ()
  (unless (executable-find "ghc-mod")
    (error "ghc-mod を利用できません。ターミナルで which したり、*scratch* で exec-path を確認したりしましょう"))
  (let ((buffer (anything-candidate-buffer 'global)))
    (with-current-buffer buffer
      (call-process "ghc-mod" nil t t "list"))))

(defun anything-c-source-ghc-mod-action (candidate)
  (interactive "P")
  (let* ((pkg (ghc-resolve-package-name candidate)))
    (anything-aif (and pkg candidate)
        (ghc-display-document pkg it nil)
      (message "No document found"))))

(defun anything-ghc-browse-document ()
  (interactive)
  (anything anything-c-source-ghc-mod))

;; M-x anything-ghc-browse-document() に対応するキーの割り当て
;; ghc-mod の設定のあとに書いた方がよいかもしれません
(add-hook 'haskell-mode-hook
  (lambda()
    (define-key haskell-mode-map (kbd "C-M-d") 'anything-ghc-browse-document)))

これで、M-x anything-ghc-browse-document を実行すると、anything のやり方でモジュールを絞り込めます。上の設定では、C-M-d に割り当てています。たとえば、"da ar" で検索すると次のようになります。


f:id:kitokitoki:20111215010949p:image


上の候補画面で RET を押下すると、ブラウザにマニュアルを表示します。


f:id:kitokitoki:20111215011330p:image


補足ですが、dot.emacs に (setq browse-url-browser-function 'browse-url-firefox) と書き、 Firefox で閲覧するよう emacs に設定しているとします。その場合、端末から firefox を起動できる必要があります。Mac だとどうやるのでしょうか。私は次のような ~/bin/firefox を作成しています。Mac には詳しくないので、もっと良い方法があるのかもしれません。

#!/bin/sh

open -a firefox.app ${@+"$@"}

それからもう一つ脱線すると、私は anything の候補画面では候補をたくさん表示したいので、dot.emacs に以下のように書いて、上下分割ではなく、左右分割にしています。

(defun anything-default-display-buffer (buf)
  (if anything-samewindow
      (switch-to-buffer buf)
    (progn
      (delete-other-windows)
      (split-window (selected-window) nil t)
      (pop-to-buffer buf))))

 flymake でシンタックスチェック

haskell-mode.el には、hlint を利用した flymake 用のしくみがあります。ghc-mod に同梱されている ghc-flymake.el では、ghc -fno-code (デフォルト)または hlint を用いた flymake 用のしくみがあります。正直なところ、両者の違いをあまりわかっていないのですが、私は ghc-mod で ghc -fno-code を使った flymake を利用しています。そのためには、haskell-mode.el での flymake の設定を無効にする必要があります。先ほどの ghc-mod の設定を次のように (flymake-mode) を含む内容に変更すれば、うまくゆきます(この方法は ghc.el の冒頭にも載っているのですが、この記事のコメント欄で教わりました)。

(autoload 'ghc-init "ghc" nil t)
(add-hook 'haskell-mode-hook (lambda () (ghc-init) (flymake-mode)))

 auto-complete での補完

auto-complete.el で、ghc-mod の M-C-i での補完機能の一部を利用した候補作成、絞り込み、挿入が利用できます。私は Version: 1.3 の auto-complete に、GitHub にある最新の auto-complete から ac-source-ghc-mod などを生成しているところを持ってきて、次のように設定しています。

;; https://github.com/m2ym/auto-complete
(ac-define-source ghc-mod
  '((depends ghc)
    (candidates . (ghc-select-completion-symbol))
    (symbol . "s")
    (cache)))

(defun my-ac-haskell-mode ()
  (setq ac-sources '(ac-source-words-in-same-mode-buffers ac-source-dictionary ac-source-ghc-mod)))
(add-hook 'haskell-mode-hook 'my-ac-haskell-mode)

(defun my-haskell-ac-init ()
  (when (member (file-name-extension buffer-file-name) '("hs" "lhs"))
    (auto-complete-mode t)
    (setq ac-sources '(ac-source-words-in-same-mode-buffers ac-source-dictionary ac-source-ghc-mod))))

(add-hook 'find-file-hook 'my-haskell-ac-init)

ちなみに auto-complete コマンドは key-chord.el を導入し、jk 同時押しに割り当てています。

(eval-after-load "key-chord"
  '(progn
     (key-chord-define-global "jk" 'auto-complete)))

右端に s と付いているのが ghc-mod から取得した補完候補です。


f:id:kitokitoki:20111215010950p:image


 定義元への移動

いわゆるタグジャンプのためのライブラリには、hasktags、hothasktags、gasbag があります。hasktags で作成したタグファイルを anything から利用する、anything-hasktags.el を書きました。anything-exuberant-ctags.el を雛形にしています。

設定例は以下になります。

(require 'anything)
(require 'anything-hasktags)

(add-hook 'haskell-mode-hook
  (lambda()
    (define-key haskell-mode-map (kbd "C-c j") 'anything-hasktags-select)))

hasketags 自体は、cabal install hasktags でインストールできます。anything-hasktags.el では、etags ではなく ctags 用のタグファイルを使います。私は .bashrc に次のような alias を書いています。Mac だと xargs のオプションが使えないかもしれません。各自で工夫して下さい。

alias hasktags-r="find . -type f -name \*.\*hs -print0 | xargs -0 hasktags -c"

以下は動作サンプルです。候補を移動するたびに定義箇所をプレビューできます。haskctags の生成するタグファイルの質がいまいちだとしても、このように一覧表示して絞り込むスタイルにすれば、有益なツールになります。


f:id:kitokitoki:20111215010947p:image

f:id:kitokitoki:20111215010946p:image


 リンク集

Google