作成: 2019-02-05T15:42:31+09:00

更新: 2019-02-12T16:07:53+09:00

EmacsのHaskellの開発環境をinteroからHaskell IDE Engineに移行しました

chrisdone/intero: Complete interactive development program for Haskell をやめて, haskell/haskell-ide-engine: The engine for haskell ide-integration. Not an IDE を使い始めました.

昔の移行記事 遅まきながらEmacsのHaskell開発環境をInteroに移行しました - ncaq

昔の記事を見て気がついたのですが, 今はターゲットの切り替えは haskell-session-change-target としてhaskell-mode標準で備えていますね.

動機 以下の機能が欲しかったからです. apply-refactによるhlintの推奨コードへの自動書き換え

領域の自動フォーマット

賢い補完

新しいプロジェクトを開くたびにinteroをビルドし直すような無駄の排除

Language Server Protocolへの統合

インストール デスクトップにはbcacheを使っていて容量に関しては割と富豪なので 何も考えずに make build-all しました. しかしロケールの問題かエラーが出ます. もう1つのインストール方法Shakeがあるのでexperimentalですがこちらを使いました. stack ./install.hs build-all

Emacsとの連携 公式ドキュメントにはlsp-mode lsp-ui lsp-haskellを使えと書いてありますが, eglot を使っている場合は何も考えなくて良いです. ( add-hook 'haskell-mode-hook 'eglot-ensure ) と書くだけで解決です. ただeglotはコードのシンプル性を重視しているのでトップレベルシンボルの一覧とか使いたい場合はlsp-modeを使った方が良いかもしれません. rustic-modeがeglot推奨になったのでこっちに統一していますが…

xmonadをstack exec経由で起動していると違うGHCのバージョンのプロジェクトで動かない問題があります 使いたいプロジェクトではエラーを出して動きません. グローバルのファイルではちゃんと動くのですが… エラー内容はこちら. 2019-02-05 15:17:07.076481989 [ThreadId 4] - run entered for hie-wrapper(hie-wrapper) Version 0.6.0.0, Git revision 7dd6fd7ab2191734ab21b502dcf6189689196cc7 (2406 commits) x86_64 ghc-8.6.3 原因はすぐわかって, 使いたいプロジェクトではGHCのバージョンが8.2.2なのに(esqueletoが新しいLTSに対応してこなかったので), ghc-8.6.3を使おうとしています. 私はxmonadを使っていて, xmonadを stack exec -- xmonad で起動しているので環境変数がグローバル向けに弄られているのでしょう. しかし, 普通にxmonadを stack exec 無しで起動させることは出来ません. なぜなら xmonad --recompile はGHCを使って xmonad.hs をリコンパイルするため, stackの環境変数が既に無いとコンパイルに失敗するからです. % xmonad --recompile XMonad will use ghc to recompile, because "/home/ncaq/.xmonad/build" does not exist. xmonad : ghc : runProcess : runInteractiveProcess : exec : does not exist ( No such file or directory ) これは xmonad --recompile を必要としないように単体でバイナリファイルを使うように書き換える必要がありそうですね. xmonad.hsでモジュール分割をする - Qiita を参考に書き換えていきましょう. xmonad --recompile を build ファイルを用意することでstackを使うように書き換える手段もありますが, stack.yaml に依存関係を書くより, package.yaml に依存関係を書くほうが, チェックツールがうまく動いてくれるので単体ファイルにするのを選びました. package.yaml name : xmonad-ncaq version : 0.1.0 synopsis : It is my xmonad and xmobar setting github : ncaq/.xmonad author : ncaq maintainer : ncaq@ncaq.net copyright : © ncaq license : MIT dependencies : - X11 - base - classy-prelude - directory - network - regex-posix - time - xmonad - xmonad-contrib executables : xmonad-ncaq : main : xmonad.hs ghc-options : - -Wall - -fwarn-tabs - -threaded - -O2 - -with-rtsopts=-N4 書き換えました. 起動には xmonad 関数ではなく launch 関数を使いましょう. xmonad 関数は結局 xmonad.hs のコンパイルを試みます. xmobarは単体の実行アプリケーションなので, インストールスクリプト install に以下のように書く必要がありました. #!/usr/bin/env zsh set -eux stack install . xmobar --flag xmobar:with_xft なんかこの辺xmobarの動的依存を stack.yaml あたりに書く方法は無いんですかね. まあ stack install の代わりに ./install を実行するだけの違いだから別に良いか… これで編集するたびに ./install を実行する必要がありますが, まあそれが本来自然ですね.

flymakeが同じディレクトリにファイルを生成して非常にうざい eglotはflymakeを使っているのですが, これが同じディレクトリにfoo_flymakeのように一時ファイルを生成して, しかも編集を終えても消去してくれないのでビルド対象に入ってしまって非常に鬱陶しい. flymakeで調べてこうしろと出てきた ( setq flymake-run-in-place nil ) を設定しても関係なく出してきますし… t の方か? と思って設定してももちろんだめです. 多分古いのでもはや使えない方法なんだと思います. でもよく考えてみるとRustだと同じくeglotでflymake使ってもこうはならないんですよね. と思って調べてみたら haskell-mode.el が設定を書き加えていました. ( defun haskell-flymake-init () "Flymake init function for Haskell. To be added to `flymake-init-create-temp-buffer-copy' ." ( let (( checker-elts ( and haskell-saved-check-command ( split-string haskell-saved-check-command )))) ( list ( car checker-elts ) ( append ( cdr checker-elts ) ( list ( flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace )))))) ( add-to-list 'flymake-allowed-file-name-masks ' ( "\\.l?hs\\'" haskell-flymake-init )) なるほどね. flymake で一時ファイルの出力先をファイルと同じディレクトリにしない - Qiita を見ればわかりますがわざわざ同じディレクトリにするように変更しているようですね. Template Haskellとかの関係で同じディレクトリじゃないとエラーが出るとかのケースに対応してるんですかね. とりあえず無効化します. ( with-eval-after-load 'haskell-mode ( setq flymake-allowed-file-name-masks ( delete ' ( "\\.l?hs\\'" haskell-flymake-init ) flymake-allowed-file-name-masks )) ) これで解決です.

apply-refactによる自動訂正がうまく動かない Template Haskellを使っているとまずまともに動きませんし. 使っていなくても保存のタイミングがおかしいですね… 2回実行する必要があります. これはeglotとの相性問題だったりするんでしょうか. GHCの警告の自動訂正はうまくいくので, hlintとapply-refactとの連携がうまくいってないようですね.

補完がcompany-indent-or-complete-commonで有効にならない company-indent-or-complete-common という, 補完が出来れば補完, 補完が出来なければインデントするというコマンドがあります. 私はタブキーにこれを使ってきました. company-modeでタブキーに補完もインデントも割り当てる - ncaq しかしどうも掛け違いがあるのか, 常に補完不能と判断されてインデントされます. 色々バグを直すことなどを考えたのですが, ｢補完が可能かどうか｣はまだエディタが判断できる問題ではないという結論に至りました. 今回に限らず補完が不能と判断されることは多いです. また｢インデントする動作｣と｢補完を行う動作｣というのは特になにか関連性のある操作というわけではありません. よって素直にキーを分けることにしました. Tab : インデント

: インデント C-Tab : モードに応じた補完

: モードに応じた補完 C-S-Tab : 単語ベースの補完 これでも既に自動補完が動き始めればTabで補完できるのでそんなに面倒ではないです.

replとエラーバッファの3分割コード interoと大して変わらず下記で実現可能です. というかもはやInterosとかHaskell IDE Engineとか関係ない気もしますが. ( defun haskell-repl-and-flycheck () ( interactive ) ( delete-other-windows ) ( flycheck-list-errors ) ( haskell-process-load-file ) ( haskell-interactive-switch ) ( split-window-below ) ( other-window 1 ) ( switch-to-buffer flycheck-error-list-buffer ) ( other-window 1 ) )

interoより少しは良い interoよりは機能豊富です. 何より一度コンパイルすれば他のプロジェクト開いてもinteroのコンパイルが不要という特徴は素晴らしいですね. しかし既にinteroが動いている中無理して移行するまでのものだったかは微妙です. まだうまく動いていない機能も多いですし. ただ補完は確実に賢くなっていました.