Haskell に IDE はないのか?──独断と偏見による Haskell の IDE 十年史
答え:Haskell に IDE はずっとある、今ならHLS使え(内容を読む気がないようという人向けの答え)
はじめに
2021年2月現在、Haskell の IDE 環境は Haskell Language Server (HLS) の登場により劇的な進化を遂げていますが、日本の Haskell コミュニティではその前身の Haskell IDE Engine (HIE) の情報がまだ氾濫しており、十分な周知に至っていない現状があります。
本稿では、こうした現状を打破すべく、2021 年 2 月現在の Haskell の IDE 環境を取り巻く現状と、そこに至るまでの歴史を完全に独断と偏見で紹介します。より多くの人に HLS の存在を周知し、皆さんの Haskell Life の一助となれば幸いです。また、HLS の前身である HIE は必ずしも快適に動作するとは言い難かったため、HLS について懐疑的な見方をしている人も多いでしょう。そうした人々の誤解を解くきっかけにもなればと思います。
以下ではこんな感じで話が進みます
- これまでの Haskell の IDE を巡る(およそ主観的な)歴史的経緯
- 何故「IDE はない」なんて言われるようになったのか?
- 追記:ただの昔語りなので、老人に興味がなかったり、何が出来るのかの方が気になる人は次の節 に飛ばしてもいいでしょう。
- HLS はなぜ成功しつつあるのか?
- ユーザ視点でのおすすめの理由
- HLS が HIE と違って成功するであろう設計上の根拠
- よくある質問と答え
- HLS {利用, 開発参加} のススメ
HLS の主な機能などは紹介しますが、HLSの具体的な使い方や、環境構築の詳しいやりかたについてはそんなに説明しません。HLS は prebuilt バイナリもありますし、VSCode なら自動インストールもしてくれるし、ghcup 経由でバイナリも取得でき、いずれにせよ簡単なので。環境ごとの詳しいセットアップの方法や機能の使い方は、HLS の README を読みつつ、実際に使っていけばわかると思います[1]。
結論を先に
- 日本語記事を検索するとたくさん出て来る haskell-ide-engine (HIE) はもう開発終了している
- HIE の後継である haskell-language-server (HLS) が安定して動き、成長速度も速いのでそっちを使おう
- VSCode の
Haskell
拡張 を使うと、Mac/Win/Ubuntuであれば最新 HLS バイナリがリリース毎に自動で降ってくるので楽- ghcup でもビルド済バイナリをインストール可能
- HLS は拡張しやすい設計をしているので、欲しい機能はどんどん実装して PR を投げましょう
- 今年の Google Summer of Code 2021 でも Haskell Language Server まわりの課題があるようなので、興味のある学生は挑戦してみよう
- 疑り深い人へ:HLS と HIE は本質的に異なる設計に立脚しており、また HIE から継続してプロジェクトに関っている方々の努力によりかなりパフォーマンスも改善しています。
- pre-built binary もあって手軽に試せるし四の五の言う前にさっさと試せや
- HLS 以外にも Dante や hhp などIDE的ツールはある
「IDE のない Haskell」と呼ばれて:Haskell IDE 前史
よく「Haskell に IDE はない」と言われることがあります。これは恐らく、「決定版と呼ばれるものがなく、どれを使えばいいのかわからない」「それっぽいものを使ってみたが、動かない」というくらいの意味でしょう。特に、鳴り物入りで現れた haskell-ide-engine
(HIE)が必ずしも快適に動くとは言い難い(婉曲表現)状態であったため、それを嘆く意図が大きいのではないでしょうか。
そうした文脈を脇によけて「Haskell に IDE がない」という主張を字面通りに受け取れば、これは露骨に偽です。期待通りに動くかを別にすれば、HIE が勿論反例になります。とはいえ、「期待通りに動くIDEがない」という主張まで緩めてみても、実は HIE 以前にも数多くの「期待通りに動く」Haskell IDE が存在し、現在も存在するという事実があります。
本節では、筆者の独断と偏見に基づき、ここ十年あまりの Haskell の IDE を巡る(不完全な)歴史を主にユーザ視点で紹介したいと思います。とはいえ、ここで採り上げるのはあくまで筆者がある程度実際に使ったことがあるもののみです。たとえば老舗の Haskell 製エディタである Leksah や Yi、あるいは Emacs 向けの簡易 IDE である ghcid や Dante、hdevtools や後述する ghc-mod の後継である hhp などは使ったことがないので採り上げません。hhp や dante は今でもメンテナンスされていますので、気になったら見てみると良いでしょう。
ghc-mod の時代──Haskell 開発環境の産業革命
Haskell の IDE を語る上で欠かせないのが、山本和彦氏によるghc-mod でしょう。筆者は 2006年からHaskell を使っていますが、往時の Haskell の開発スタイルといえば、コードを書いてはビルド&GHCi で読み込み・手直し、といった感じで、そこに(Emacs であれば)flycheck-haskell
を入れて警告・エラーなどの診断をソースコード内に自動で表示する、といった極めて原始的なものでした(それでも flycheck-haskell
は私にとっては革新的で助かっていましたが)。
そういった状況を変えたのが ghc-mod でした。ghc-mod は GHC API を使って書かれた最初期の IDE と言えるでしょう。GHC API というのは、GHC の処理系としての機能(構文木のパーズ、名前解決、型検査、脱糖、Core へのコンパイル、インタプリタ……)をライブラリとしてまとめ再利用できるようにしたもので、各処理系と一緒に配付されている ghc
パッケージで提供されています。今日では Haskell の開発補助ツールでは GHC API を使うことが一般的になっていますが、この機能が正式リリースで初めて登場したのは GHC 6.6 [2]の頃、2007年のことだったようで、Cabal パッケージとしてまとめられて特別なドキュメントが提供されるようになったのは、GHC 6.10 からでした。比較的すぐだったかのように見えるかもしれませんが、当時の GHC は一年に一つメジャーバージョンが上がればよい方、という感じであったため、6.6 が出たのが 2007年4月、6.10.1 が出たのは 2008年11月でした。
そのような背景を受けて、2010年頃に登場したのが ghc-mod です。GHC API の機能不足などと格闘しつつも順調に機能が増え続け、「現在位置の型を取得」「HLint によるリンティング」「定義へのジャンプ」「Template Haskell マクロの展開」「構文・型検査情報の取得」「シンボルの補間」「文脈に応じた修正の適用」「ローカルの Haddock ドキュメントの検索」など多種多様な現代的な開発補助機能を提供するようになりました。
補完については、それまで Haskell 開発では何らかの形で TAGS を自動生成させ、それを使ってなんとなーく補完をする──というの方法が主流でした。これは、ローカルで定義されているシンボルであれば補完が効きますが、外部ライブラリの補完には必ずしも使えません。一方、ghc-mod はインポート宣言のところでは利用可能なモジュールの情報から補完候補を生成し、シンボルの補完ではインポートされたものも含め、現在のスコープ内にあるシンボルから候補を推薦してくれ──とより正確で精密な補完が可能でした。シンボルの補完については、モジュールを追加する度に情報更新の命令を出す必要があったのが若干不便といえば不便でしたが、それを補って余りあるアドバンテージでした。
また、「文脈に応じた修正」の機能が非常に便利で、例えば未定義関数のエラーに対して適用すれば、適切な型註釈と共にダミーの定義を挿入してくれ、不完全なパターンマッチの警告に対して適用すれば欠けているパターンを補ってくれ──と正に痒い所に手が届く素晴しいものでした。
ghc-mod の登場により、Haskell の開発環境は劇的に進歩し、旧石器時代から産業革命まで一気に進んだ。そういっても過言ではありませんでした。
stack という黒船──Haskell 開発の激変
この後も長く ghc-mod の天下が続いた──かといえば必ずしもそうではありませんでした。
というのも、2010年代中盤から IDEに限らず Haskell の開発環境は大きな変化を経験することになるからです。折しも、2010年には Haskell の Web フレームワークの大御所 Yesod が登場し、これを一つのきっかけとしつつ Haskell の産業利用が加速していきました。様々なライブラリやアプリケーションが現れ、Hackage 上のパッケージも活発にバージョンが更新されるようになります。また、GHCの開発じたいもアジャイルな方向にシフトし、そうした流れの中で、Haskell のパッケージ管理ツールである cabal (厳密には実行ファイルとしての cabal-install
)に対して、コミュニティの中で徐々に不満が溜まってくるようになりました。
というのも、当時の cabal-install
は ~/.ghc
以下にコンパイル済の各パッケージのバイナリをすべて(GHCのマイナーバージョンごとに)一枚岩の状態で保存し管理していたからです。こうした状況で、Haskell コミュニティは Cabal Hell あるいは dependency Hell と呼ばれる危機に慢性的に直面するようになります。これは、いわばオブジェクト指向でいうところの菱形継承のようなものです。さまざまなバリエーションがありますが、以下の状況を想定しましょう:
-
package-A
:package-C
に依存している -
package-B
:package-C
に依存している
ここで、更にpackage-A
と package-B
両方に依存する my-package
を新たにビルドすることを考えます。ところが、a
, b
はそれぞれ別々のタイミングで独立してコンパイルしたため、実際には以下のような状況に陥っていたとします:
-
package-A
:package-C
のバージョン0.1.0.0
に依存している -
package-B
:package-C
のバージョン0.2.0.0
に依存している
この状態で、my-package
をビルドすると、my-package
はバージョンの異なる二つの package-C
に依存することになります。ここで、次のような状態を想定しましょう:
module LibC where
data TypeInC = TypeInC
module LibA where
valC :: TypeInC
module LibB where
consumeValC :: TypeInC -> Bool
つまり、 package-C
で定義されている型 TypeInC
に対して、package-A
では TypeInC
型の値が定義され、 B
では TypeInC
を引数として消費する関数が定義されているとします。
ここで、もし my-package
に次のようなコードがあったとしたらどういなるでしょうか?
module MyLib where
import LibA
import LibB
consumedBool :: Bool
consumedBool = LibB.consumeValC LibA.valC
型が合っているのでコンパイルが通り──そうに見えますが、LibA.valC
は package-A
が依存する package-C-0.1.0.0
で定義された TypeInC
の値を返しているのに対して、LibB.consumeValC
は 0.2.0.0
のものを要求しているので、異なる型という扱いになり、コンパイルに失敗してしまうのです。つまり、大域的なパッケージデータベースにパッケージのバイナリが登録されたタイミングによって、数字上ではバージョン制約の解消に成功してもコンパイルに失敗することがあり得る訳です[3]。
この問題は、以下の二点が原因です:
- 一枚岩のパッケージデータベースを共有しており、異なるバージョン制約のパッケージが共存し得ること(矛盾したバージョン制約の許容)
- 不整合なバージョン制約を持つパッケージ同士が(警告はあっても)互いにリンクされてしまうこと
そこで、各プロジェクトごとにサンドボックス化(隔離化)されたパッケージデータベースを使ってビルドする cabal-dev
というものが出て来たり、cabal-install
自身も sandbox
機能を搭載したり、といったことがありました。
しかし、これらは標準パッケージ以外の依存関係すべてをプロジェクトごとにその都度ビルドするものであったため、あっという間にディスク容量が食われてしまうという問題がありました。また、完全に不整合なバージョン制約を禁止する訳でもなかったので Cabal Hell が再現し得るものでもありました。更に、広義の Cabal Hell として、「数字上はバージョン制約が整合的に解決できたとしても、範囲指定が緩すぎて途中でコンパイルエラーが起きてしまう」という問題は、こうした手法では解決できません。
そこで2015年6月に登場したのが、今日では標準的な Haskell ビルドツールとして用いられている stack です。stack は Cabal Hell の解決のみならず、ビルドの再現性という点にも重きを置いて設計されていました。また、cabal sandbox のように stack も隔離環境下でパッケージデータベースを管理しますが、条件が揃うのであれば可能な限りビルド結果をキャッシュして再利用するという設計になっていました[4]。また、不整合なバージョンは不許可なので Cabal Hell は原理的に起き得ません。これと、遅れて登場した、ビルドが一通り通る事が保証された選定済パッケージのバージョン一覧=ビルドプランを提供する Stackage を組み合わせることで、少なくとも大部分の範囲のパッケージについては、完全に Cabal Hell から自由なビルド体制が可能になったのです[5]。また、それまでの cabal-install
にはなかった特徴として、複数のパッケージからなるプロジェクトが一つの stack.yaml
で定義・運用可能になった、という利点もありました。
これらは現在ではすっかり当たり前になりましたが、当時かなり画期的なことでした。
……IDEの話なのになんでビルドツールの話をしているの?という声が聴こえて来そうですが、漸くここで話が IDE ツーリングに戻ってきます。stack
はパッケージ管理にはライブラリとしての Cabal
を使ってこそいますが、 cabal-install
とは著しく異なるディレクトリ構成を持つため、それまでの cabal
や cabal sandbox
を前提として設計されていたツールが動かないという問題がありました。なら cabal-install に戻ればいいのか……といえば、いちど stack という禁断の果実を味わってしまえば、いつ Cabal Hell に襲われるのかわからない cabal-install には戻りたくない、というのも人情です[6]。
このツール回りの激変の影響を受けたのは、ghc-mod
も同様でした。とはいえ、ghc-mod チームの対応は素早く、2015年8月末には最初の stack
対応版のリリースが行われていました。しかし、それでももともと cabal-install
を前提として設計されていたこともあり、当時はまだ一部の機能が期待した通りに動かなかったり、ghc-mod 以外のツールとの連携が上手くいかなかったりといった問題も出て来ました。
私は stack ベースのエコシステムに完全に乗り換えていたこともあり、苦渋の決断でしたがこの辺りで ghc-mod の利用を一旦やめてしまいました。これらの問題は、後ほど解消されたようで、ghc-mod
はメンテナ交代の後、後述する HIE のベースとなるライブラリとして 発展解消しています。また、IDE としての ghc-mod は原開発者の山本さんによって hhp という形で復活し、8.10.3 でも通常通り動いているようです。
群雄割拠の時代へ──そして VSCode という新たなる黒船
こうした開発環境の激変と、加速された GHC のリリース間隔の影響で、「定番で」「あらゆる環境で動く」Haskell の IDE は第一義的には存在しなくなり、群雄割拠の時代へと突入しました。そうそう、GHCの進化が加速し、GHC API がどんどん変化していくようになったのも、個人開発されていた既存の開発ツールのメンテナンスが追い付かなくなった一つの原因だったと思います。特に、GHC 8.0 前後で Haskell の型システム回りは大きな変革を経験し、また GHC API まわりに関しても、構文木回りが Trees That Grow という手法により次々とリファクタリングが進められるなど大きな変化が継続的に起きました。そうでなくとも、GHC はメジャーバージョンごとの進化に伴って結構 API が変化するので、個人で変更に追随するのはなかなか大変だったりします。
泣く泣く ghc-mod
を離れた私が次に試したのは、intero でした。Intero は Chris Done 氏 が stack の利用を前提として設計された Emacs 向けの Haskell IDE で、ghc-mod の機能には幾分足りないものの、一方でリアルタイムの型検査や stack のビルド対象パッケージ切り替え、定義へのジャンプ、補完、型の表示など、必要とされる機能の大部分はサポートされており、Emacs 版のクライアントでは必要な intero のバージョンを適切に自動ビルドする機能もついていました。Emacs 向けとはいっても、ghci を拡張する形で実装されていたため、別のエディタでも使えました。
そして、これと前後するように、2015年末には VisualStudio Code が登場しました。私は暫く Emacs + intero + その他個別のHaskell開発ツールのクライアント、という形で開発環境を運用していましたが、肥大化しスパゲッティ化する Emacs LISP 群を管理するのがいやになり、2018年頃に VSCode へと移行しました。ここで移行を決断した理由の一つには、intero のクライアントが VSCode にも存在したということがありました。とはいえ、二つあるクライアントのうち一つは動かず、もう一個の動くほうの Haskero は、ちゃんと動かすにはインストール後にコードを書き換える必要があり、またEmacs 版に比べれば機能が制限されていたり、特に自動インストールをしてくれない、といった不満はありました。それでも、警告・エラーの表示や補間などはある程度機能していましたので、HLint を適用する拡張や、stylish-haskell を適用する拡張を一緒に入れ、これらを組み合わせる形でなんとか Haskell の開発環境を運用していました。
それでも、このままなんとか intero をベースとした開発環境を利用しつづけられるかとも思われましたが、intero は Chris Done 氏の個人開発であり、氏が GHC 8.6.5 より先のバージョンへの対応はせず開発を終了することが2018年末に発表されました。それでも、8.6.5 までのコードで使えましたので、暫く騙し騙し使いつづけていました。
HIEの時代──失敗した天下統一
こうした流れと並行して、2015年にはhaskell-ide-engine (HIE) のプロジェクトが発足していました。ghc-mod や FPComplete の ide-backend、またリファクタリングツールの HaRe などこれまで分散していたIDEへの試みを結集して、コミュニティで協業をしてIDEの決定版を作ろうという試みでした。一時開発は停滞しましたが、その後 2017年の Language Server Protocol (LSP)の登場を受けて再び再開したという経緯があります[7]。
ご存知の方も多いとは思いますが、LSP はエディタや言語によらず、IDE の汎用的な機能を実現するためのプロトコルです。VisualStudio Code の登場を契機に、Microsoft が主体となって策定し、オープンソースで保守・拡張が続けられている仕様です。この仕様に従って言語サーバを実装すれば、LSP に対応していればどんなエディタでも使えるというもので、現状では VSCode を筆頭に、Emacs、Vim、Atom を含む多くのエディタで(拡張などの形で)サポートされています。HIE も、自前のエディタとの通信プロトコルから LSP に乗り換えることで、多くのエディタで使えるようになった訳です
実は、HIEについて私が言えることは余り多くありません。というのも、「Haskell 開発環境」や「Haskell IDE」で検索すると(2021年の今でも!)HIE に関する記事が多くヒットするにもかかわらず、必ずしも現実的[8]なプロジェクトで動くとは限らなかったためです。より厳密を期すのであれば、最初の数分程度は機能するものの、徐々にレスポンスが遅くなり、最終的には全く変更に追随しなくなる、といった按配でした。こうなると Language Server のプロセスを kill -9
して再起動するより仕方ありませんでした。数分おきに kill
するのも手数ですし、またその度にロード時間もかかりますので、実用的な開発には残念ながら向かない、というのが私の正直な感想でした。この「実用的な開発には向かない」というレベルの挙動が長く続いたことも、おそらく今日「Haskell に IDE はない」という言説が一定程度の説得力をもってしまう要因でしょう。
では、HIE は大失敗だったのか、といえばそうではありません。HIEが開発される過程で、以下のようなパッケージが整備され、今でも活用されています:
- 汎用のLSP実装パッケージ
- 言語サーバのテスト記述用のパッケージ
- cabal や stack に限らず、より一般のビルドシステム[9]で適切に IDE を起動するためのフラグ情報を管理する
hie-bios
- Extended Interface File(
.hie
ファイル)の策定 - IDE のコア機能をプラグインで拡張するというデザイン
- その過程で蓄積された、GHC API をパッケージ化した種々のコード
HIE という先人がなければ、こうしたインフラの整備は進まなかったことでしょう。
hie-core
)の登場
HLS の足音──ghcide (旧 名乗りを上げた HIE が思うように天下を統一できず、Haskell の IDE 情勢は依然として動乱期にありました。この状況を変える契機となったのは、2019 年のhie-core
(現・ghcide
)の登場でした。
hie-core
は Neil Mitchell 氏を中心に開発が進められた新たな IDE でした。また、新たな IDE か……と一瞬思うかもしれません。しかし、 hie-core
で取られた戦略は、これまでの IDE 実装の試みとは一線を画すものでした。それは、IDE を一種のビルドシステムとして実装するという思想です。
何を隠そう、産みの親である Neil Mitchell 氏は、Haskell 製の汎用ビルドシステム Shake の開発者でもありました。
Shake は Haskell コードとしてビルド規則を記述する Make の代替で、効率性やビルドのキャッシュ化、動的な依存関係の正確な取り扱いなど、優れた性質を備えています。直接使ったことがなくても、GHC ユーザであれば誰もが必ず恩恵を受けている筈です──というのも、GHC の開発に使われているビルドシステム Hadrian は Shake を使って実装されているからです。興味があれば、Shake の原論文 "Shake Before Building" や [Hadrian の原論文]https://www.microsoft.com/en-us/research/wp-content/uploads/2016/03/hadrian.pdf)を読んでみるとよいでしょう[10]。
確かに、言われてみれば IDE はある種のビルドシステムです[11]。コードから適切な情報を取り出すために依存関係をビルドし、そこから型やドキュメントの情報などを取得する。ビルド過程で生成されたインタフェースから必要な情報を取り出し、後で使えるように保存する。途中でビルドが中断しても、キャッシュを用いて即座に再開し、あるいは中間的なデータを返す。まあ、ビルドという言葉を使ってるのでズルですが、結果から言えばこういった機能をビルドシステムのものと捉えることで統一的に記述出来るようになり、拡張性もグンと上がったのです。実際にどのような形で実装が進められ、どういった効果が得られたのかは、"Building an Integrated Development Environment (IDE) on top of a Build System: The tale of a Haskell IDE" に詳しく述べられています(二年前ですので、古くなっている内容もありますし、結局FRPではなく shake ベースで落ち着いています)。
hie-core
はその名に反して(HIE のコード資産を使いつつ)HIE の開発チームとは独立に開発されていましたが、名に示されている通り、HIE のコア部分を置き換えたい、という意図の下開発が進められていました。直接 HIE 開発チームと(当時の時点では)の関りはないということを明確化するためか、hie-core
はその後 ghcide
と名を変えています。
2019 年当時、私は開発終了がアナウンスされた intero に頼って日々の開発を行いつつ、8.6.5 以降は対応しないしどうしよう、と開発環境に不安/不満を感じていました。そんな中 ghcide
がアナウンスされ、諦め半分で早速試してみました。当時は型検査の警告程度しか機能はありませんでしたが、社の 5万行、25パッケージからなる型ハックマシマシのプロジェクトに使ってみたところ、かなり軽快に動いたのでかなりびっくりしたのを覚えています。
これは ghcide の登場で Haskell の IDE 情勢が急変するかも……と思っていつつ動向を注視していたところ、遂に 2020年1月、Bristol Hackathon で遂に HIE と ghcide の統合が合意されます。Haskell Language Server (HLS) の誕生です。
ghcide に可能性を感じていた私は、思わず次のようなツイートをしてしまいました。
結論から言えば、二つ目のツイートは杞憂に終わりました。ghcide の軽快さと、HIE の高機能性を兼ね備えた最強のIDEが誕生したのです。
HLS は何故成功しつつあるのか?
ここまで非常に主観的な、HLS史観とでもいうべき Haskell IDE の(約)十年史を概説してきました。以下では、「なんでそんなに HLS を推しているのか」ということをお話ししたいと思います。
それには HLS の設計思想についてお話するのが一番ですが、その前に単なるユーザとして見た時に HLS を利用する利点について列挙しておこうと思います。あくまで概要であって全機能紹介ではないので、「なんだこの機能ないならいいや」とかはやとちりしないで、機能などは以下の公式ページを参照するとよいでしょう。
-
環境構築の容易さ
- ビルド済バイナリの提供:GHC 8.6.4 以降の全ての GHC のマイナーリリース(追記:本記事公開直前にリリースされたので忘れてましたが、9.0.1対応はもう少しかかります)に対して、Linux, macOS, Windows 向けのビルド済バイナリが用意されています。
- 更に、VSCode の Haskell extension は、これらの最新リリースをGHC バージョンとOSに応じて自動的にダウンロードしてくれます!
- ghcup を使ってビルド済バイナリのインストールも可能
-
機能の豊富さ
- 完璧ではないにせよ、存在する・これまで存在した Haskell IDE に比して遜色ないか一部ではより便利な機能がサポートされています。
- コード補完、警告・エラーのハイライト、マウスオーバーで型やドキュメントを表示、定義へのジャンプ、リファクタリング(限定的)、case-split、importリストの自動拡張、型から式を生成、Template Haskell マクロの展開、コメントに埋め込まれた形での簡易REPL……
- その他これからあなたが実装するであろう機能
- README が追い付いてないほど機能がたくさんあります。
-
メジャーな開発環境で動く
- OS は Linux, macOS, Windows など主要な OS で動きます
- stack や cabal-install で管理されているプロジェクトであれば、ディレクトリ構成が複雑でない限り設定なしで動きます
- 動かなくても、
hie.yaml
を書くか、gen-hie
で生成すればだいたい何とかなります - rules_haskel などは HLS を使うための設定を提供しています
- 正しく設定をすれば、どんなビルドシステムであれGHC でビルドされているプロジェクトなら原理上動く
-
開発コミュニティの規模と成長速度の速さ
- 複数の人々が協業する形で、コミュニティベースでの開発が進められている
- 旧 ghc-mod や hdevtools、intero などのような、個人開発であった為に、メンテナのやる気がなくなると開発が終了してしまったり滞ったりしてしまったり、ということは起きづらいでしょう。
- Release ページを見てもわかるように、開発チームだけでなく、どんどん外部からの Pull Request も増えていっています。今機能が足りなくても、ガシガシ開発し成長していくでしょう。
- 複数の人々が協業する形で、コミュニティベースでの開発が進められている
-
メンテナンス性の高さ
- 次の節で触れるように、HLS はかなり拡張しやすい設計になっています。コードが肥大化・複雑化して継続的なメンテナンスが不可能になる危険性は、今のところかなり低いでしょう。
- 前の要素とも関わりますが、メンテナンス性の高さは拡張の高さにも直結していて、これも HLS の急成長を支える要因の一つになっています。
- 残りのインフラ部分はHIEからの遺産ですが、基幹部分の刷新の成功が一番大きい。
-
パフォーマンスへの意識
- GSoC のプロジェクトなどにより、かなり詳細なベンチマークスイートやメモリプロファイルの手法が確立されています
- その甲斐あってかなり反応速度は良く、バージョンを追うごとにメモリ使用量やレスポンス時間は劇的に改善しています
- 実体験:5万行、25パッケージ型ハックマシマシみたいなプロジェクトでも、(自明にビルドにかかる時間を除けば)かなり軽快に動作していますし、メモリ使用量も実際にビルドする際の使用量に比べて不当には高くない程度に収まっています。
- 反応速度が速いので、稀に再起動の必要性に迫られても、
kill -9
せずとも再起動用のコマンドを打てば旧 HIE と違ってすぐ動作する状況になるのでほとんどストレスはありません。
……アンチパターンみたいな長さの箇条書きになってしまいました。まあ、とにもかくにも、まずは使ってみると良いのではないでしょうか。
では、次から「なんでこれが実現できてんの」という話をしていきましょう。
HLS の基本構造──ビルドシステムとプラグインによる高い拡張可能性
上述したように、HLS は ghcide と HIE が合流する形で成立しました。古い記述がインターネット上にまだまだ残っていることもあり、両者の関係については時々混乱が見られます。簡単にいえば、ghcide を核としてそれを拡張したものが、現在の HLS になっています。HLS は、概ね以下のような階層構造をしています:
- コア機能を提供する ghcide
- 同一パッケージだが、更に以下の二つに分割すると理解しやすい
- IDE の諸機能に必要な情報をビルド/キャッシュするためのビルドシステムと情報取得API
- プロジェクトのビルドに必要なフラグやパスなどを
hie-bios
を使って汎用的に取得する - そのフラグを使って必要に応じて関係パッケージ&モジュールをビルドし、IDEに必要な情報を取得する
- パーズ後の構文木、型検査の結果、インポートされたデータetc
- プロジェクトのビルドに必要なフラグやパスなどを
- それを用いて実装された最小限の IDE 機能(シンボル・モジュール補完、Haddock の表示、欠けている型制約の追加機能、警告やエラーの表示など)
- この機能群も最終的にはプラグインとして分割される予定(のはず)
- IDE の諸機能に必要な情報をビルド/キャッシュするためのビルドシステムと情報取得API
-
注:かつては
ghdide
の実行ファイルも配付されていたが、現在は HLS の開発実験用途のみで、ユーザはghcide
実行ファイルを LSP 越しに呼んで使うことは想定されていない(全く問題なく出来るが)
- 同一パッケージだが、更に以下の二つに分割すると理解しやすい
- プラグイン群:ghcide の機能を使って実装されたより進んだ IDE 機能群
- 例としては以下のような機能がある:
-
Eval Plugin: 簡易REPL。モジュールコメント内の
>>>
型 doctest を GHCi のように実行可能 -
Tactic Plugin:
case
式によるパタンマッチの自動生成や、型から式を自動生成する機能 -
Splice Plugin: Template Haskell のマクロを展開する機能(僕がつくりました (((o(^^)o))) )
-
HLint Plugin: HLint を使った linting と修正の適用
-
Explicit Import Plugin: シンボルのインポートを
()
内で陽に指定する形に置き換える -
Class Plugin: インスタンス定義で欠けているメンバ関数のダミー定義をしてくれる
-
Retrie Plugin: Retrie を使ったリファクタリング
-
Haddock Comment Plugin: 関数などに自動で Haddock コメントのプレースホルダを付与してくれる
-
Module Name Plugin: モジュール名を自動で生成・修正する
-
コードフォーマッタ類:ormolu, forumolu, stylish-haskell, brittany, floskell などの
-
- 例としては以下のような機能がある:
-
haskell-language-server
: ghcide 上で上記のプラグインを(フラグに応じて)有効化した「全部盛り」の IDE- フラグを指定すれば、不要なプラグインを無効化したり、自作のプラグインを追加したりできる
- 機能は最小限で良い、という場合は全部フラグオフにして野良ビルドすればよい
模式的には、「haskell-language-server = IDE特化のビルドシステム ghcide + その上の種々のプラグイン群」と理解すればよいでしょう。
プラグインの仕組み自体は HIE の頃からあったようですが、コア部分がビルドシステムとして整備されたという変更が最も本質的な差異であり、HLSを成功に導く最後の要素であったといえると思います。それ以外のインフラ部分については、旧HIE時代の資産を有効活用し、遂に成功した、といった感じです。
ビルドシステムとしての ghcide が非常によく設計され、プラグイン機構も非常に綺麗に抽象化されているため、HLS に新しい機能を追加するのは非常に簡単で楽しい作業になっています。たとえば、これは完全に宣伝ですが、私がこの(2020〜2021)年末年始に開発しマージしてもらった Splice Plugin は、実装を開始してからおよそ2日ほどで基礎的な実装を終えることができました。それ以前に Eval プラグインへの :type
や :kind
コマンドの追加や、Tactics プラグインの生成コードから辞書引数を除くバグ修正などをしたことがあったとはいえ(露骨な宣伝2)、新しいプラグインをゼロから書くのはこれが初めてでしたが、プラグイン実装のチュートリアルや既存実装を参考にしつつ、スムーズに実装を進めることができました。当初実装したロジックは急拵えだったので、その後 HLS の中の人達から色々と有益なアドバイスを頂いて、ghcide の内部を拡張する形でより合理的な実装に改良し、無事一週間ほどでマージして頂けました。開発チームの人達は非常にオープンな人達で、活発に議論してコミュニティとして開発を進めていけるのでとても気持ちが良いです。その後も Eval Plugin のデバッグ改修をやってみたり、業務中に欲しくなったので曖昧なインポート済シンボルの一意化
の実装をして PR を投げてみたり、ということをしていたら、Collaborator 権限を頂いてしまいました。今後も頑張ります!
なんか自慢ばかりでうざったくなってきましたね。ここで強調したいのは、HLS はビルドシステムとそれを用いたプラグインとしてIDEを実現するというアーキテクチュアを採用することで、安定性を保ちつつ拡張性とメンテナンス性を高めることに成功しているということです。皆さんも、こんな機能が欲しい!と思ったら、ぜひ自分で実装を試みてみてください。
hie-bios: ビルドシステムと独立したビルド情報のセットアップ
前章で、Haskell の IDE 情勢はビルドツールの変化に大きな影響を受けてきたことに触れました**。HLS は今のところ巧くいっているようだけど、今後はどうなの?**というのは当然抱いてしかるべき疑問です。
結局のところ、ビルドツールの変遷によって IDE が上手く動かなくなるのは、特定のビルドツールのアーキテクチュアに依存した実装になってしまうためです。ですが、一般的な Haskell 開発において、以下の二点は仮定してもよいはずです:
これらの仮定の下では、各モジュールに対して(パッケージDBのパスを含む)必要なコンパイラフラグ、それから依存パッケージをビルドする方法(コマンド群)がわかっていれば問題ないはずです。
こうした情報の解決を行うのが、hie-bios です。旧HIE で ghc-mod の cabal-helper をより汎用的に置き換えることを目的に開発されました。hie-bios は基本的には cradle file(ゆりかごファイル)と呼ばれる hie.yaml
で定義された条件を基に、各モジュールのコンパイルフラッグなどの情報を割り出します。
たとえば、stack
で管理されているプロジェクトであれば、hie.yaml
の内容は以下のようになります:
cradle:
stack:
- path: "src"
component: "my-great-package:lib"
- path: "test"
component: "my-great-package:test:great-test"
- path: "app"
component: "my-great-package:exe:great-app"
- path: "greater/src"
component: "my-greater-good-package:lib"
え、これ毎回書かなきゃいけないの?と思ったかもしれませんが、御安心ください。こうした情報は、多くの場合** ghcide 内部で自動的に推論される**[14]ので、stack や cabal-install ベースのプロジェクトなら、複雑な構成でなければ基本的に hie.yaml
を書く必要はないはずです[15]。「target が見付からない」と怒られたら、その時は書く必要がありますが、implicit-hie
パッケージの gen-hie
実行ファイルを使えば、ある程度自動で生成してくれるので、それを手直しすればいいでしょう。hie.yaml
の詳しい書式は hie-bios
のドキュメントを参考にしてください。ちゃんと hie.yaml
を書けば、cabal と stack が部分的に併存するようなパッケージや、超自前のビルドシステムを使っていても問題なく動くはずです。
これらの情報が使われるのは ghcide の時点ですので、「この開発ツール/ライブラリは ghcide で動きます」と書いてある場合、各プラグイン機能が動作するかは別として HLS も同様に機能することが期待されます(rules_haskell など)。
.hie
ファイル)の活用
Extended Interace File (GHC 8.8 以降では、IDE などの開発補助ツールでの利用を念頭に、コンパイル時に拡張インタフェースファイルを生成する機能が追加されています。これは、単純化された構文木にシンボルの定義場所や型などのメタデータを付加したもので、開発ツールが軽量に読み書き出来るように設計されています。拡張インタフェースファイルの拡張子は .hie
ですが、これは haskell-ide-engine... ではなく、haskell interface extended の略ということになっています。最近は専ら stack や cabal 越しに使う事が増えているため直接目にする機会は減っていますが、GHC はモジュールをコンパイルすると基本的に拡張子 .o
のオブジェクトファイルと .hi
のインタフェースファイルを出力します[16]。後者をより IDE の使い易い形にし、必要なメタデータを付与したASTを吐くようにしたものが .hie
と考えて良いです(.hi
とデータ構造がそのまま拡張されている訳ではありませんが)。拡張インタフェースファイルの策定は、前述したように旧 Haskell IDE Engine チームからのフィードバックを受けて行われたものです。
GHC 8.6 以前では拡張インタフェースファイルを生成してくれないので、HLS では ghcide のために開発された hie-compat
というライブラリを使い、8.6 以前の場合は自前で生成してそれを使うようになっています。この拡張インタフェースファイルをある種のキャッシュとして活用することで、HLS は各種機能が一定以上遅延しないようにしています。HLSではIDEの機能に必要な情報(パーズ結果、型検査の結果、シンボルの位置情報など)を取得する戦略として「現在のソースを愚直に処理して情報を取得する」「現在のソースから情報を取得しようとして、失敗する(たとえば構文エラーがあるなど)ようなら古い情報を使う」とか「古くていいから手早く手に入る情報を使う」という複数の選択肢があります。実装する機能によってどの戦略で情報を取得=ビルドの依存関係を解決すべきかは変わりますが、型の表示やジャンプなど、速さが要求されるアクションについては、拡張インタフェースファイルのキャッシュを使って、フォールバックないしショートカットするようになっているわけです。
こうして生成された hie ファイルは、プロジェクトのディレクトリではなく、基本的に全ていっしょくたに ~/.cache/ghcide
ディレクトリに保存されています[17]。別のツールで情報が欲しくなった場合はそちらを覗いてみると良いかもしれません。
また、これは2021年2月5日現在まだリリースされていませんが、hiedb パッケージの機能が HLS に組み込まれる予定です。これによって、.hie
ファイルに基づいてインデックスが生成されるようになり、プロジェクトのロード時間がかなり短縮されます。また、シンボルの定義箇所を Command+Click すると、その型ないし値の出現箇所が複数モジュールに亘って一覧できるようになるようです。
素晴しいですね。最初つかってみたとき便利すぎてびっくりしました。すごい。順調にいけば、2021年2月中のリリースでこの機能が使えるようになるはずです。
よくある質問と回答
長々とHLSが成功する要因について語ってきましたが、未だHLSは「いま著しく成功しつつある」途上であり、当然まだ幾つか不具合や落とし穴があります。以下では、独断と偏見でそうした FAQ を掲載しておきます。
動的リンクを使っているプロジェクトで動かないんだけど
現状、GHC は静的リンク、動的リンク、プロファイリング、クロスコンパイルなどの際にそれぞれ違う内部形式を使っています。
静的リンクされたバイナリの場合は静的リンクされたGHC、動的リンクされたバイナリの場合は動的リンクされたGHCしかコンパイルできません[18]。この制限は内部で GHC APIを使ってバイナリをロードしている HLS も例外ではありません。つまり、動的リンクを前提としたプロジェクトの場合は、HLS自体も動的リンクでビルドしないと動かないようになっています。
公式のビルド済バイナリは静的リンクされたものなので、動的リンク前提のプロジェクトに使う場合は、自前で動的ビルドをすれば基本的に動く筈です。ただし、hie-bios が種類の判定に失敗すると上手くいかないこともあるらしい。
HLint Plugin が全く動かないんだけど
これは #591 で報告されていて、#1225 で修正されたので、最新の 0.9.0 リリース以降は直っている筈です。
これは HLint Plugin が依存しているライブラリの upstream bug で、ビルド時に使った GHC のライブラリパスがハードコードされてしまうことが原因でした。何か宗教的な理由で 0.9.0 を使いたくない場合は、#591 を参考にしてください。
TemplateHaskell を使っていると時々動かないんだけど
わかる。よくあるのは、
Exception when trying to run compile-time code:
thread killed
Code: ...
}
みたいなやつですね。こういう場合は HLS を再起動すると、エラーが消えてくれる事が多いです。再起動しないといけないのは悲しいですが、ものすごい変更が多いとかでない限り、読み込み時間は少なく抑えられる筈なので、我慢してください。HIEのことを再起動が必要だと悪口いっといてなんやねんという感じですが、あっちはそもそも読み込みも遅く動作ももっさりしていたし手動で kill -9
する必要があったのでストレスフルだったのに対し、HLSではよほど大規模であったりしない限りは変更点をリビルドする以上の待ち時間はかからない筈で、再起動のコマンドも提供されているので、まあ許容範囲だと思います。
それでもかなり広い範囲の Template Haskell を使ったコードがビルド出来るようになってきているので、動かない例を見付けたら関連 issue を参照しつつ、どの例も当て嵌まらないようなら報告して貰えると助かります🙏
Overlappingとかが絡むとインスタンスがちゃんと解決されない時がある
なんでだろうね……。そういえば issue ある筈とおもったけどなかった。minimal case を見付けたらこちらで issue 開きますが、遭遇して小さな再現例つくれたら issue 切っといてください。
マウスオーバーで Haddock が表示されない
Haddock 読みたいパッケージの ghc-options
に -haddock
フラグを渡すようにしてパッケージをビルドしてやる必要があります。ただ、GHC 9 未満だったか 8.10 未満だったかだと、このフラグを渡すと Haddock の書式にエラーがある(別にシンボルのドキュメントではないのに -- |
になってるなど)場合、パッケージ自体のコンパイルがエラーになってしまうので、泣く泣く通るパッケージかローカルのパッケージでだけ指定するか、十分新しい GHC を使うようにしてください。後者はまだキツいと思うが……。パッケージのバージョンがあがって作者が HLS を使っていたりするとコメントが修正されてコンパイル通るようになってる可能性もあるのでその手もある。
ライブラリの変更が同じパッケージのテスト/ベンチに波及しないんだけど
HLS を Restart するなどして、再度リコンパイルすると反映されます。それぞれ別々のコンポーネントとしてビルドされているので、現状ではどうしてもこうなります。multi-component package / project の扱いにはまだまだ改善の余地がある(それでも個人的には target を切り替える必要のあった intero よりよほどよい)ので、改善策などあったら気軽に Contribute を!
依存パッケージがめちゃくちゃ沢山あるとかではない限り、Restart してリビルドはそんなに時間かからないはずです。前述の hiedb の変更が入ったのでさらに短縮もされてはいるはず。どうしても時間かかる!という場合は次の節を参考にしてください。
読み込みにすごく時間がかかるんだけど
幾つか要因が考えられます。
- HLSと同じ設定で同時に裏でビルドやテストが走っており、ディレクトリがロックされている
- 依存関係のビルドに時間がかかっている
1 を避けるためには、ビルドやテストが終わるのを待つか、さもなくば HLS の hie.yaml
で違うディレクトリを使ってビルドするように設定しておくとよいでしょう。同じライブラリが二度ビルドされることになるため、多少容量のオーバーヘッドはありますが、フラグなどがかわらなければキャッシュが使われ、それほど消費しないはずです[19]。
たとえば、stack をビルドに使っている場合、以下のような内容で stack-ide.yaml
を作成し、
resolver: lts-16.25
packages:
- .
work-dir: .stack-ide # .stack-work ではない
hie.yaml
で以下のように設定すれば、別のディレクトリ .stack-ide
でビルドされるようになります:
cradle:
stack:
stackYaml: stack-ide.yaml
# components: ... #
Git などで管理されている場合は、**/.stack-work
と同様に **/.stack-ide
を .gitignore
や .git/info/exclude
に追加しておくとよいでしょう[20]。
2 は、HLSに限らない、本質的に待たなくてはならない時間です。なので、HLS 用のビルドの設定のみ -O0
をつけるなどして、極力ビルドにかかる時間を節約するようにしておくとよいでしょう。理想的には -dynamic
をつけたくなりますが、HLSは前述のように動的ビルドとの食い合わせが悪いので、まあ -O0
が落とし所でしょう。とはいえ、本ビルドと違うフラグを使うことになるため、ディスク容量の使用量はどうしても増えてしまいます。
あと、何度も言及してますが、体感では hiedb の変更がマージされた HLS はかなり読み込みが速くなる場合があるように思える(慎重な表現)ので、新版が出るのを待つか、自前で master ブランチをビルドして使うのもアリです。
結語 - Haskell Language Server {利用, 開発参加} のススメ
「Haskell の IDE に関する誤解を解く」「HLSを周知する」という当初の目的に比してなんだか無駄話が多くどんどん長くなってしまったし、そんなに Haskell の IDE 全てを網羅出来ているわけでもないですが、っていうか全然出来てねえですが、Haskell の IDEの現状とそこに至るまでの経緯をなんとなく理解してもらえたら嬉しいです。
とりあえず覚えて帰ってほしいことは、
- Haskell Language Server はマトモに動くだけでなくかなりモダンで高機能、導入も簡単
- HLS はかなり保守・拡張しやすい構造になっているので、積極的に contribute してみよう!
といったところです。そうそう、Google が OSS に取り組む学生に一夏奨学金を出すプログラムである Google Summer of Code 2021 でも、HLS をはじめとして Haskell のIDE 環境を改善するプロジェクトが提案される予定なので、興味のある学生の皆さんは是非積極的に参加してみるとよいでしょう[21]。学生じゃない皆さんにお金は出ないですが、それはそれとして何か機能実装してプルリク投げると楽しいんじゃないでしょうか。おすすめとしては、以下の機能があるといいですね:
- Eval Plugin の
:set
コマンドで、-X
コマンド以外も書けるようにする(#1278) - Eval Plugin は Tutorial の説明に相違して
:{
,:}
で囲まれた複数行入力に対応していないので、対応すると嬉しいなと思います。 - HaRe Plugin をつくる
- Retrie Plugin を拡張するか hiedb などを活用するなどして、変数名の変更などの進んだリファクタリング機能を実装する(#282)
- 拡張インタフェースファイルを使った静的解析ツール Stan を組み込む(#258)
- 本物の REPL をつくる(#477)
- GHCi を追加的に起動してロードしなくても、HLS が内部的に既にモジュールの情報をロードしているので、そこから追加作業なしで REPL が立ち上げられると嬉しいね、という感じです。
- あなたの欲しい機能
- その他このテーブルにあるやつ
学生の人もそうでない人もぜひこんな機能が欲しい!を形にして、Haskell の開発環境を爆上げするこのビッグウェーブにのっていきましょう。
Happy Haskelling!
-
README を読めばわかることが沢山インターネットに書かれた結果、README が読まれなくなり、人類は百年前のREADMEに基づいて書かれたやってみました記事に永劫苦しめられる呪いを受けた。負の連鎖はここで断ち切ろう。README を読め。 ↩︎
-
8.6 じゃないよ 6.6 だよ! ↩︎
-
こうした例の他にも、依存する複数のパッケージのバージョン指定の範囲が広すぎ、インターフェースの整合性が取れるバージョン制約解消が困難な場合も Cabal Hell と呼ばれます。 ↩︎
-
まあ、それでもかなり容量は食うんですが、それでも大分マシです。こないだ
rm -rf ~/.stack
したら 50GB くらい空いたので笑ってしまいました。 ↩︎ -
この方向性は Nix-style local build と呼ばれ、最近
cabal-instal
でもcabal v2-build
など new-style command として利用出来るようになりました。ビルドプランを一行で指定することはできませんが、cabal.project
を使えば、stackage.org
からダウンロードしてきたバージョン制約を与えて、それを使うよう強制することもできます。ちなみに、Stackageはどのように維持されているのかというと、パッケージの作成者が自作のパッケージ群を stackage のキュレーションリスト上に入れる Pull Request を出し、日次CIで実際にコンパイルが成功すれば Nightly に登録され、最終的に次にリリースされる LTS に含まれることになります。実際にコンパイルが通るかは自動で確認されますが、バージョン番号などの選定は人力で行っており、多数のボランティアと自分のパッケージを Stackage に入れておきたいというメンテナの熱意によって成り立っています。 ↩︎ -
今日では、
cabal.project[.local]
ファイルのようなプロジェクト毎の制約・設定ファイルや、stack-like なcabal new-build
がcabal-install
でも利用可能になっていますが、これがこれが登場したのは一年後の 2016 年 で、機能が安定するのはもうすこし後でした。この辺りの年度感は、記事を書いていて思ったよりcabal-install
の対応も早かったんだな、という感じがしますが、まあ冷静に考えて一年も待てませんよね。 ↩︎ -
ここで、現実的、というのは主観的な尺度です。私の経験では、ある程度以上のモジュール/パッケージを含んでいたり、Template Haskell や少し凝った型レベルプログラミングをしていたりするともうだめでした。 ↩︎
-
たとえば Bazel を使って実装されている rules_haskell や、GHCの専用ビルドシステム Hadrian など、用途に合わせて様々なビルドシステムが存在します。 ↩︎
-
もっといえば、Hoogle と HLint の開発者でもあります。 ↩︎
-
上掲のShake 原論文の続きとも言える "Build Systems à la Carte" ではなんと Excel すらビルドシステムとして扱われています。この論文では、様々なビルドシステムの依存関係グラフの特性(動的なのか、静的なのか、失敗から復帰し得るのか…etc)を関手の制約(
Functor
なのか、Applicative
なのか、Monad
なのかMonadPlus
なのか……)を関連づけて分類しており、とても面白いので気になったらぜひ読んでみてください。 ↩︎ -
GHCJS はここでいう GHC に含まれませんが、jsaddle を使えば GHC と GHCJS ハイブリッドでビルドできるのでこの条件を満たします。JVM 上の eta とかではどうなんでしょうね。わからん。GRIN など新しいコンパイラも出現してきているけど、GHC をフロントエンドに使えるからまあ大丈夫なんだろうか。 ↩︎
-
hpack による
package.yaml
だって最終的にはcabal
ファイルに変換される。Nix はよくわからんが、cabal
ファイルから依存パッケージ一覧は取得している筈だし、実際 Nix support もあるのでまあ大丈夫なんでしょう。 ↩︎ -
この部分を担うのが
implicit-hie-cradle
ライブラリです。gen-hie
実行ファイルを提供するimplicit-hie
とは別ライブラリです。 ↩︎ -
基本的に一つのパッケージ(ライブラリ、exe、bench、test は複数あってもよし)だけからなる場合なら何もしなくてもいいはず。 ↩︎
-
基本的に、と書いたのは、動的リンクだと後ろに
_dyn
がついたり、プロファイリングが有効だと_p
がついたりするので。 ↩︎ -
今となっては必要とされることのないバッドノウハウですが、かつてはghcide / HLS の反応が悪くなったらこのディレクトリを消すという対症療法が知られていました。もう長いことやってないです。 ↩︎
-
特に考えずに GHC で動的リンクと静的リンクを切り替えられるのは、GHC がデフォルトで動的/静的両方のバイナリを含む形でビルドされ頒布されているから。これはクロスコンパイル時にめちゃくちゃ不便なので何とかしようという話がある。詳細はちゃんと把握出来ていないので、公式のWiki記事 を参考にしてほしい。 ↩︎
-
とはいえ、マウスオーバーで Haddock ドキュメントを表示するには、依存関係に
-haddock
GHC オプションを渡す必要があるので、全体で有効にしていない場合はこの時点でフラグが変わっちゃうんですけどね。 ↩︎ -
別のやり方としては
.stack/ide
を指定する手もありますが、ビルドしたことのない外部ライブラリを HLS ではじめてビルドする場合、stack の仕様なのかビルドに失敗します。別ディレクトリにしておくほうが安全でしょう。 ↩︎ -
GSoC に Haskell.org が採択されなくとも、多少規模は小さくなっても Haskell organisation 独自で Haskell Summer of Code が開催される通例があるので安心を。 ↩︎
Discussion