さて、この記事では VSCode と stack を利用した Haskell の開発環境の構築を行っていきます。この環境構築を飛ばしても、次回以降の記事を読むのに支障はありませんが、コードを実行・評価できる環境が整っていると学習が捗るのでおすすめです。
- 概要
- Stack のインストール
- VSCode拡張:Haskell のインストール
- implicit-hie のインストール / hie.yaml の自動生成
- JetBrains Mono フォント(任意)
- おすすめの学習方法
- REPL の基本と設定
- おわりに
- VSCode トラブルシューティング
- あとがき
概要
インストールする対象は以下のとおりです。
最近(でもないですが)の VSCode は Remote Containers が登場したこともあり、ナウなヤングは Docker を使って開発環境を構築する人も多いのかもしれません。しかし、Remote Containers は Preview 段階ですし、Docker は Mac のバッテリー消費も激しいので、この記事では直接マシンにインストールしていきます(トラブルに自分で対処できる人は Remote Containers でも良いかもしれません)。
なお、VSCode と Homebrew はすでにインストール済みと仮定して進めていきます。
Stack のインストール
Stack は Haskell におけるビルドツールのようなものです。
大雑把に言うと、コンパイラである GHC とパッケージマネージャである Cabal のラッパーみたいな感じになっています。現時点では、とりあえず Stack さえ入れておけば Haskell で開発する環境は整うということだけ抑えておけばよいでしょう。
Homebrew でインストールできます。
$ brew install haskell-stack
次に stack setup
を実行すると、グローバルプロジェクトの設定が行われ、GHC が利用できる状態になります。
$ stack setup
GHC には REPL(対話的にコード片を試せる環境)が用意されており、この時点で利用できる状態になっています。任意のディレクトリで stack ghci
すると起動できます。
$ stack ghci ... Prelude>
もしかすると Prelude>
の部分は表示が異なるかもしれませんが、現時点では気にしなくて大丈夫です。
せっかくなので、少しだけ利用してみましょう。
Prelude> 1 + 1 2 Prelude> length "Hello, GHC." 11
これで単純な Haskell コードであれば REPL で試せるようになりました。REPL 上で :q
と入力するか、Ctrl + D
で REPL を終了することが出来ます。
Prelude> :q Leaving GHCi.
REPL の基本的な使い方については後述したいと思います。
ちなみに余談ですが、Swift でちょっとしたコードを試すときには Playground を利用することが多いかもしれませんが、Swift でも REPL は提供されています。
$ swift ... 1> 1 + 1 $R0: Int = 2
VSCode拡張:Haskell のインストール
VSCode拡張:Haskell から Install ボタンをクリックするか、VSCode 上から検索してインストールしましょう。
Haskell は Language Server が開発されており、この拡張もそれを利用して動作するようになっています。Language Server はこの拡張をインストールすると一緒に自動的にインストールされるので、別途インストールする必要はありません。
せっかくなので新しく Haskell プロジェクトを作成して VSCode 上で確認してみましょう。 stack new
コマンドを利用して新しい Haskell プロジェクトを作成することが出来ます。
$ stack --resolver lts-17.2 new hello-world
—resolver
というオプションは今は考えなくてもよいのですが、気になる方のために少しだけ説明しておくと GHC とパッケージの構成を指定するものです。省略した場合は自動的に最新の構成(resolver)が利用されるのですが、執筆時点において最新のバージョンである LTS Haskell 17.4 (ghc-8.10.4) を利用すると GHC 8.10.4 が使用されるのですが、前述の Language Server が 8.10.3 までしか対応していないため、少し前のバージョンを指定しています。
さて、そんな細いことはさておき、VSCode で開いてみましょう。
$ cd hello-world $ code .
「code
コマンドが使えないよ!」という方はVSCode 上から普通に開くか、VSCode 上で Cmd + Shift + P
でコマンドパレットを表示して Shell Command: Install …
を選択して code
コマンドを利用可能にしましょう。
VSCode 上でツリーを展開すると、次のような構成になっているかと思います。
試しに Lib.hs
を開いて、putStrLn
という関数にカーソルをホバーさせてみましょう(初回は Language Server の起動に少し時間がかかるかもしれません)。
試しに新しい行に put
と入力すると、コード補完も機能することが確認できます。
いい感じですね!
implicit-hie のインストール / hie.yaml の自動生成
さて、おそらく先ほどの VSCode 上での手順を進める際、VSCode の右下に以下のようなポップアップが表示されたかと思います。
現時点で Haskell Language Server は hie.yaml
という設定ファイルを必要とする作りになっています。先ほど見てきたように、hie.yaml
が無くても動作はするのですが、構成によっては正しく動かなくなってしまう可能性もあります。
hie.yaml
は自分で記述してもよいのですが、自動生成してくれる implicit-hie という Haskell 製のツールが用意されています。
以下のように stack install
コマンドでインストールできます。
$ stack install implicit-hie ... Copied executables to /Users/xxxx/.local/bin: - gen-hie
コマンド名は gen-hie
とツール名と異なっているので注意しましょう。 ~/.local/bin
にインストールされるので、パスを通していない方は ~/.zshrc
などに追記しておくと良いでしょう。
export PATH="$HOME/.local/bin/:$PATH"
さて、 hie.yaml
を生成しましょう。
$ gen-hie > hie.yaml
これで No cradle found for …
というポップアップは表示されなくなったかと思います。
ただし、ファイル構成を変更した場合は再生成が必要になることもあるので、なんだか VSCode の調子がおかしいなと思ったら、とりあえずこのコマンドを再実行しておくとよいかと思います1。
JetBrains Mono フォント(任意)
Haskell のコードでは記号が非常に多く登場します。そのため、合字(リガチャ)に対応したフォントの方がコードが見やすいという人もいるかもしれません。
例えば、以下のようなコードの場合、
合字に対応した JetBrains Mono を使用すると以下のようになります。
どちらが見やすいかは完全に好みですが、記号がゴチャゴチャしてコードが読みづらいと感じたら試してみても良いかもしれません。
VSCode で JetBrains Mono を使用する手順は以下のとおりです。
- JetBrains Mono をインストール。
- VSCode 上で
Cmd + ,
で設定画面を開く。 - Font Family の項目の先頭に
'JetBrains Mono',
を追加する。
おすすめの学習方法
大体の Haskell 入門書では、最初に REPL を活用した解説が出てきます。
REPL は短いコードを試すのには便利なのですが、複数行の関数を定義するようになってくると REPL では辛くなってきます。また、REPL では基本的に書き捨てになってしまうので、自分で書いたコードを後から見直したいときにも不便です。
ここでは、個人的におすすめな学習方法を書きたいと思います。
REPL の活用
先ほど VSCode 上で開いた Lib.hs
が格納された src
ディレクトリに、新しく Calc.hs
を作成して次のコードを入力してみましょう。
module Calc where -- 拡張子を除いたファイル名と合わせる add :: Int -> Int -> Int add x y = x + y
ここではコードの解説は行いませんが、1行目の module xxx where
の xxx
の箇所はファイル名と合わせることだけ覚えておきましょう(お察しのとおり --
はコメントです)。
次に Shift + Ctrl + ^
で VSCode のターミナルを開き、 stack ghci
を実行しましょう。
$ stack ghci ... *Main Calc Lib Paths_hello_world>
途中で Warning が表示されたりするかもしれませんが、とりあえず REPL が使用可能になれば大丈夫です。
プロジェクトのルートディレクトリで stack ghci
を実行すると、プロジェクトに含まれるコードが import された状態で REPL が起動します。そのため、先ほど定義した add
関数が利用できます。
*Main Calc Lib Paths_hello_world> add 1 2 3
いい感じですね!
REPL は開いたままで、新たに sub
関数を追加してみましょう(保存を忘れずに!)。
module Calc where add :: Int -> Int -> Int add x y = x + y sub :: Int -> Int -> Int sub x y = x - y
REPL では :r
を入力することでリロードが行われるので、 追加した sub
関数も利用可能になっています。
*Main Calc Lib Paths_hello_world> sub 1 2 -1
いい感じですね!
このように事前に REPL を起動した上で、
- ソースコードを編集。
:r
で再読み込みして、REPL で評価。
というループを構築しておくと、コードを書き換えながら試すことができて学習効率が良いかと思います。
なお、新しいファイルを追加したときは :r
による再読み込みでは反映されないので、:l ファイル名
で明示的に読み込むか、REPL を再起動する必要があります。
テストコードの活用
新しいプログラミング言語や API を学ぶ際には、テストコードも一緒に記述すると使い方が記録として残るので効率的という話を聞きます。ここではドキュメンテーションコメント上で記述できる、手軽なテストコードの書き方について紹介しておきます。
Haskell には doctest というドキュメンテーションコメント上にテストを書けるツールが存在します。
以下のコマンドでインストール出来ます。
$ stack install doctest
実行するには stack exec doctest {対象ファイル名}
というコマンドを使用します。試しに、先ほど作成した Calc.hs
に対してテストを実行してみます。
$ stack exec doctest ./src/Calc.hs Examples: 0 Tried: 0 Errors: 0 Failures: 0
まだ何もテストが存在しないため、1つも実行されていないのが分かります。
試しに add
関数にテストを記述してみましょう。最初は意図的に失敗させます。
-- | 足し算 -- >>> add 1 2 -- 0 add :: Int -> Int -> Int add x y = x + y
ドキュメンテーションコメントは -- | {関数の説明}
から開始します。関数の説明は省略しても良いのですが、-- |
を書き忘れるとテストとして認識されないので注意しましょう。テストは >>>
の後に実行したい式を記述し、直後の行に期待結果を記述します。
もう一度、テストを実行してみましょう。
$ stack exec doctest ./src/Calc.hs ./src/Calc.hs:4: failure in expression `add 1 2' expected: 0 but got: 3 ^ Examples: 1 Tried: 1 Errors: 0 Failures: 1
add 1 2
という式を評価し、 0
という結果を期待したものの、実際には 3
が返却されたため、テストが失敗しているのが分かります。
期待値を正しい 3
に変更して、テストがパスすることを確認しましょう。
$ stack exec doctest ./src/Calc.hs Examples: 1 Tried: 1 Errors: 0 Failures: 0
いい感じですね!
なお、>>>
を使ったテストは複数記述できます。
-- | -- >>> add 1 2 -- 3 -- >>> add 0 1 -- 1 add :: Int -> Int -> Int add x y = x + y
また、詳細についてはここでは割愛するのですが、>>>
の代わりに prop>
を使用することで QuickCheck を利用した Property-based Testing も同時に記述できます。
以下は、足し算が「結合法則を満たす」ことを検証しています。
-- | -- >>> add 1 2 -- 3 -- prop> add x y == add y x add :: Int -> Int -> Int add x y = x + y
Property-based Testing については、以前 try! Swift 2019 TOKYO で発表したスライド(英語)や Qiita 記事があるので、興味のある方はご参照ください。
ちなみに、VSCode の Haskell 拡張をインストール済みの場合、>>> 式
だけ記述して Evalute...
をクリックすると評価結果が自動的に生成されます。
生成された後は Refresh...
ボタンに変わっているので、クリックすると再評価されます。
エディタ上の簡易な REPL 的な使い方をしても良いかもしれません。
REPL の基本と設定
さて、最後に REPL について軽く説明しておきたいと思います。
ヘルプ
まず :?
でヘルプを見ることが出来ます。
*Main Calc Lib Paths_hello_world> :? Commands available from the prompt: <statement> evaluate/run <statement> : repeat last command :{\n ..lines.. \n:}\n multiline command ...
利用可能なコマンドが列挙されるので、何か分からないことがあれば :?
でヘルプを見ると良いでしょう。
代表的なコマンド
ここでは代表的なコマンドを列挙したいと思います。
まずは REPL 全体の操作に関するものです。
コマンド | 説明 |
---|---|
:load |
指定したファイルの読み込み |
:reload |
リロード |
:module |
モジュールの追加・削除 |
:quit |
REPLを終了 |
`:help | ヘルプを表示(:? と同等) |
次に Haskell のコードに関するものです。
コマンド | 説明 |
---|---|
:type |
型を表示 |
:kind |
カインドを表示 |
:info |
識別子の情報を表示 |
よく分からないものも多いと思いますが、それは必要なときに説明されると予想されるので(著者に期待したいところです)、現時点では理解しなくても大丈夫です。
さて、先ほど :r
でリロードできましたが、実は :reload
の短縮形だったのです。REPL では :xxx
とした場合、最初にマッチしたコマンドが選択されるようになっているため、大体のコマンドは1文字で入力できます。:q
なら終了ですし、:t
なら型を表示しますし、:k
ならカインドを表示します。
複数行の入力
REPL 上で複数行にわたるコードを記述したい場合には :{
で開始して、 :}
で終了します。
以下のような感じですね。
*Main Calc Lib Paths_hello_world> :{ *Main Calc Lib Paths_hello_world| mul :: Int -> Int -> Int *Main Calc Lib Paths_hello_world| mul x y = x * y *Main Calc Lib Paths_hello_world| :} *Main Calc Lib Paths_hello_world> mul 2 3 6
プロンプト表示のカスタマイズ
さて、そろそろプロンプトの表示がムダに長いと感じてきた頃でしょう。
*Main Calc Lib Paths_hello_world>
並んだ文字列から予想された方もいるかも知れませんが、これはファイルが増えるほど長くなっていき、非常に見づらくなってしまいます。
:set prompt
と :set prompt-cont
でカスタマイズすることが出来ます。
*Main Calc Foo Lib Paths_hello_world> :set prompt "> " > :set prompt-cont "| " > :{ | x = 1 | y = 2 | :}
ご想像のとおり :set prompt
は通常のプロンプト、 :set prompt-cont
は複数行時のプロンプトをそれぞれ設定できます。
しかし、REPL を起動するたびに入力するのは面倒という人も多いでしょう。~/.ghci
に記述しておくことで自動的に反映されるようになります。
:set prompt "λ " :set prompt-cont "| "
私は上記のとおり λ
記号を使用しています。なんだか通っぽい感じがでますよね?
おわりに
さて、今回のまとめです。
- Stack は Haskell におけるビルドツール。
- VSCode の Haskell 拡張を入れれば、Language Server も一緒に入ってすぐに使える状態になる。
stack new xxx
でプロジェクト作成。No cradle found for …
という通知を消したければhie.yaml
を作成する。- implicit-hie を使えば自動生成できる。
- お好みで JetBrains Mono などの合字に対応したフォントもオススメ。
- VSCode と REPL を活用した学習方法。
- doctest を使うとドキュメンテーションコメント上にテストを書ける。
- REPL のヘルプは
:?
で見られる。 - 複数行の入力は
:{
で始めて、:}
で終える。 - プロンプトは
:set prompt
と:set prompt-cont
でカスタマイズできる。 ~/.ghci
に記述しておけば自動的に反映される。
さて、次回は関数の基本的な書き方を見ていく予定です。
VSCode トラブルシューティング
おまけとして、VSCode のトラブルシューティングについて記載しておきます。
補完やホバーが動かない
考えられる要因と対策は以下のとおりです。
- Language Server の起動に時間が掛かっている。
→ 待つ。 - Language Server がサポートしていない GHC バージョンを利用している。
→--resolver
で明示的にバージョンを指定する(記事中のlts-17.2
であれば動くと思います)。 hie.yaml
の定義が正しくない。
→gen-hie > hie.yaml
で更新する。
それでも解決しない場合は、VSCode のログなどを確認してみると良いでしょう。
なんかコード全体がエラー扱いに・・・
とくに GHC の言語拡張 {-# LANGUAGE ... #-}
を記述しようとすると起こることが多いのですが、コード全体に赤ラインが引かれてまともに動かなくなってしまうことがあります。
その場合は、VSCode を再起動すると直ることが多いようです。
あとがき
コードを試したくなったら REPL だ。REPL を使わないと損だぞ!
Written by tobi462.
-
本格的に開発する際には git コマンドにフックしても良いかもしれません。↩