neco-ghc で補完時に型情報を表示できるようにした

ghc-mod のほうに補完に使えそうな機能追加があったので久しぶりに neco-ghc を更新した https://github.com/ujihisa/neco-ghc

vimrc で let g:necoghc_enable_detailed_browse = 1 とすると補完時に型情報が表示されるようになる。 ghc-mod 1.11.5 で追加された機能を使っているので、有効化するにはそれ以上のバージョンが必要。

変数だけではなく型クラスや型シノニム等にも表示される。

欠点としては、無効化したときと比べてやや初期化に時間が掛かることが挙げられる。 キャッシュされるので最初さえ我慢できれば従来通り高速に補完候補が表示される。

ghcmod.vim 0.1.0

ghcmod.vim 0.1.0 をリリースした。 https://github.com/eagletmt/ghcmod-vim/downloads からダウンロードできます (リポジトリ内のファイルからプラグインとして利用する上で不必要なものを除いて zip で固めただけ)。

前回 からの変更点について書いていく。

:GhcModExpand コマンドを追加した

ghc-mod 1.10.8 で追加された ghc-mod expand のためのインターフェイス。 Template Haskell の展開の様子を quickfix に表示する。

ちなみに、ghc-mod expand と同等の出力は ghc -ddump-splices でコンパイルしても得られる。

GHC や hlint にオプションを渡せるようにした

ghc-mod は GHC や hlint を利用しており、それらにオプションを渡す機能が備わっている。 これらをそれぞれ g:ghcmod_ghc_optionsg:ghcmod_hlint_options でちゃんと設定できるようにした。

例えば :GhcModLint 時に "Redundant $" というヒントを出させないようにするためには以下のように ~/.vimrc で設定する。

let g:ghcmod_hlint_options = ['--ignore=Redundant $']

非同期チェックをサポートした

なぜ自動チェックが嫌いなのか - eagletmt's blog なんてことも書いたけど、 ブロックしない自動チェックならば便利だと思うので quickrun での非同期実行の実装を参考にして非同期的に :GhcModCheck:GhcModLint を実行できるようにした。 それぞれ :GhcModCheckAsync:GhcModLintAsync というコマンドを用意している。 さらに両方同時にチェックする :GhcModCheckAndLintAsync というコマンドも用意した。

バッファ保存時に自動的に実行するには ~/.vimrc で

autocmd BufWritePost *.hs GhcModCheckAsync

とか設定するといいと思う。

lint はともかく、check のほうは import しているモジュールが多かったりメタプログラミングをしていたりすると 結構時間が掛かるので、効果があるんじゃないかと思う。

ただし現状の非同期実行はちょっとロバストでない点があって、もしかしたらエラーを出してしまうかもしれない。 このへんは近いうちに改善したい…

Cabal を利用しているプロジェクトでの動作を改善した

Cabal はビルド時にやってくれていることがいくつかあって、それらに依存したプログラムのために 適切なオプションを GHC に渡すことで :GhcModCheck:GhcModType でエラーが出ないようにした。

具体的には、cabal build

  1. 依存パッケージに対してバージョンのテストを行うマクロを提供したり、
  2. 自身のパッケージに関する情報を提供する Paths_pkgname というモジュールを生成したり、
  3. 拡張子に応じて適切な前処理を実行したり

しているので、そのためのパスを設定したりしている。これら3点に関しては考慮しているつもりだけど、他にも何かあったりするのかな。

注意点としては、これらは cabal build 時に行われるので、一度 cabal build を実行して dist/build 以下にファイルを生成しておかないと意味がない。 1, 2 に関しては一度生成してしまえば後から変わることは無いので問題にはならないけど、 3 に関してはチェックの度に前処理が実行されて欲しいと思う。 単純な .cabal ファイルであれば https://gist.github.com/2227993 で前処理のみ実行できると思うけど、 Build-type: Simple ではない場合はどうするのがいいのかわからない。

Haskellでいかにしておっぱい画像をダウンロードするか〜2012 Conduit 編

元ネタはここらしい: いかにしておっぱい画像をダウンロードするか〜2012 - ゆーすけべー日記

D言語でいかにしておっぱい画像をダウンロードするか〜2012 — Gist を見かけたので Haskell 版を書いてみたんだけど、どうやら既にやられてしまっていたようだ…

いかにしておっぱい画像をダウンロードするか~2012 Haskell編 - 厨二病患者のプログラミング入門

まぁ今流行りの(?) Conduit 版だとこんなかんじ、という例として見てもらえれば。今気付いたけど TupleSections は特に有効にする必要なかったな…

ghcmod.vim と Unite hoogle の組み合わせ

ghcmod.vim でカーソル位置の型を得ることができるため、これを Unite hoogle の入力としてみると便利そうだと思った。

例えば ~/.vim/ftplugin/haskell.vim

nnoremap <buffer> <silent> <Space>o :<C-u>call <SID>unite_hoogle_type()<CR>
function! s:unite_hoogle_type()
  let [_, l:type] = ghcmod#type()
  call ghcmod#type_clear()
  call unite#start(['hoogle'], { 'input': l:type })
endfunction

を追加する。

すると、よくわからない位置に undefined を書いてとりあえずコンパイルは通る状態にした後、

undefined の位置で <Space>o と打鍵すると

こうなる、みたいな使い方がありそう。

まぁ undefined に限らず、型でも検索できる hoogle と、型を得ることができる ghc-mod type の組み合わせは良さそう。

unite-haddock に hoogle の結果を使う source を追加した

以前インストールされているモジュール名を候補とする unite-haddock を書いた が、 それに加えて hoogle がインストールされていれば :Unite hoogle で hoogle の結果を候補として表示できるようにした。 アクションは haddock と同じで、ローカルあるいはリモート (Hackage) のドキュメントをブラウザで開くというもの。

https://github.com/eagletmt/unite-haddock

準備

hoogle が必要。cabal install hoogle でインストールできる。

さらに hoogle のためのデータベースを作る必要がある。 hoogle data で初期化した後、必要に応じて hoogle data $pkgname でデータベースを作って hoogle combine *.hoo で default.hoo を更新していくといいと思う。

使い方

unite.vim のインターフェイスで hoogle による検索結果を見ることができる。

ここから型や関数を選んで、そのドキュメントをブラウザで開くことができる。

:Unite hoogle だけでなく、:UniteWithCursorWord hoogle でカーソル位置にある単語で検索するのも便利かもしれない。

設定

g:unite_source_hoogle_max_candidates に候補として表示する結果の数の最大値を設定できる。 デフォルトでは 200。 これは unite.vim とは独立して (hoogle 側で) 最大値を設定しているため、Unite バッファ上で <Plug>(unite_toggle_max_candidates) を打鍵しても影響が無い点に注意。

また g:unite_source_haddock_browser にドキュメントを見るためのブラウザを設定できる。 多くの環境であれば勝手に適切なコマンドを用いて開いてくれるので設定する必要は無い。 しかし一部の環境では file:///path/to/doc.html#fragment という URI を開く際に /path/to/doc.html#fragment というパスのファイルを開きにいってしまうため、この変数を用意した。 具体的には、awesome のようなマイナーな DE でかつ xdg-open が利用可能なとき、xdg-open がそのような挙動をしてしまう。

他の hoogle を利用しているプラグイン

unite-haskellimportref-hoogle がある。 どちらも ujihisa さんによるもの。

ref-hoogle が機能的に近そうだが、ref-hoogle は単に hoogle での検索結果を表示するだけであり、ドキュメントを開きにいく機能は無い。 あと現状あんまりまともに動いていない気がする……

neco-ghc が neocomplcache 無しでも利用できるようになりました

neco-ghc は neocomplcache 専用のプラグインでしたが,構造を変更して neocomplcache 無しでも Vim のオムニ補完の関数として動作できるようにしました. neocomplcache ユーザにとっては表面上の変更点はありません.neocomplcache の有無にかかわらず,補完能力そのものは同じです.

ujihisa/neco-ghc - GitHub

necoghc#omnifunc を 'omnifunc' に設定することで,非 neocomplcache ユーザでも neco-ghc による補完を Vim のオムニ補完として使えます. ~/.vim/ftplugin/haskell.vimsetlocal omnifunc=necoghc#omnifunc と設定すると Haskell のコードを開いたときに自動的に 'omnifunc' に設定できます. そしてインサートモードで C-x C-o とすれば補完できます.

以下は neocomplcache 専用のプラグインから 'omnifunc' としても使えるように書き換えたときにちょっとハマった部分の記録.Vim の補完関数を書いたり,それを neocomplcache からも使えるようにしたい人には参考になるかも.

補完のキャンセル

'omnifunc' の仕様は :help complete-functions にある. a:findstart が 1 のときは補完位置を返すわけだけど,

Return -1 if no completion can be done, the completion will be cancelled with an error message. Return -2 to cancel silently.

と書かれている. これを読んで俺は「-1 を返すと補完はキャンセルされるので a:findstart が 0 で再び呼ばれることはなく,さらに -2 を返すと『パターンは見つかりませんでした』のエラーメッセージも出ない」と解釈した. しかしどうやら,-1 を返したときに関しては a:findstart が 0 で再び呼ばれるのが仕様らしく, そのとき a:base は空文字列になっている. 一方 -2 を返したときは呼ばれない. なので,とりあえず neco-ghc では補完できないときは位置として -1 を返し,a:base が空のときは補完できないものとして空の補完候補を返している.

col('.') の値

a:findstart が 0 で 'omnifunc' が呼ばれたとき,col('.') は「補完位置」を返す. つまり,例えば

foo ba_

において _ の位置でオムニ補完をして,補完位置として ba の先頭を返した場合,補完候補を計算する段階では col('.') の値は 7 ではなく 5 となる.

一方 neocomplcache のプラグインとして動作するとき,s:source.get_complete_words() の内部では col('.') の値は 7 になる. そのかわり(?),s:source.get_complete_words() の第一引数で補完位置として 4 が渡される.

ghc-mod の Vim プラグイン ghcmod-vim を書いた

kazu-yamamoto さんによって開発されている ghc-mod というコマンドとそのための一連の elisp がある. ghc-mod を利用して Vim で補完をサポートする neco-ghc を以前書いたが, 今回は ghc-mod のその他の機能をサポートする Vim プラグイン ghcmod-vim を書いた.eagletmt/ghcmod-vim - GitHub

必要なもの

当然 ghc-mod が必要なので cabal install ghc-mod でインストール. ~/.cabal/bin 等に ghc-mod が置かれますが,そこに正しくパスが通っていて実行可能であることを確認してください. 特に GVim, MacVim を使っていると,シェルでの環境変数GVim, MacVim での環境変数が異なるケースがあるので…

そして vimproc が必要です. 最近 vimproc に追加された checkpid() という関数を使っているため,git pull で最新版を使うことを勧めます. フォールバックは用意しているので,ver 6.1 でも動作します.

機能

:GhcModType, :GhcModTypeClear

Haskell のコードを開き,型を知りたい場所の上にカーソルを移動して :GhcModType とすると,その部分がハイライトされて型が表示されます. 型を表示できるのは単なる式だけでなく,束縛やパターンマッチのパターンにも使えます. また,ハイライトされている位置で再び :GhcModType とするとハイライトの範囲が変わります. スクリーンショットは github のほうの README.md を見てください. ハイライトは :GhcModTypeClear で消せます.

ハイライトの色は g:ghcmod_type_highlight に設定することができます. デフォルトでは 'Search' によってハイライトされます.

本当はバッファ毎にハイライトを設定したいのだけど,難しそうだったためハイライトそのものは現在のウィンドウのみで有効になる. ハイライトしている範囲はバッファ毎に保存される.

:GhcModCheck, :GhcModLint

:GhcModCheck とすると,現在のバッファで開いている Haskell のコードに対してコンパイラのエラー・警告を quickfix ウィンドウに表示します. 何らかのエラー・警告があったときは自動的に quickfix ウィンドウを開きます (勝手に :cwindow します). 同様に :GhcModLint とすると,hlint からのメッセージを quickfix ウィンドウに表示します.

現時点で最新版である ghc-mod 1.10.9 では ghc-mod check はファイルパスではなくファイル名を出力するため, :GhcModCheck は一時的にカレントディレクトリを変更してから setqflist() するという方法でファイル名と実際のファイルパスを関連付けている. しかしこの方法では import しているモジュールが別のディレクトリにあり,そちらのほうでエラー・警告が出た場合に対処できない. これは ghc-mod の問題だと思う. 対策としては,import 先のモジュールもそのプログラムの一部なわけだから, そちらのほうで先に :GhcModCheck して警告が出ないことを確認しておく,というある意味当然な方法がある.

さいごに

ghc-mod type がとても便利そうだったので,そのときの衝動で一気に書いた.

Haskell そのものに慣れていなかったり,利用している関数の型が複雑だったりするときに「ここにどんな型の式を書けばいいんだ???」となることがある. そんなときに,とりあえずその位置に undefined を書いてコンパイルは通るコードにした後,undefined の上で :GhcModType として型を見る,というような活用方法もあると思う.

不具合の報告や機能の追加・変更の希望を待ってます. github の issue に登録したり,Twitter で @eagletmt に報告したり,ここのコメントに書いたりすれば気付きます.