🙆

おすすめHaskellプロジェクト作成方法(ほぼ)2021年版

2020/12/31に公開
1

はじめに

(あともう少しで)あけましておめでとうございます。

この記事では2020年12月31日時点での私のおすすめの最新のHaskellプロジェクトの作成方法をまとめます。

Haskellの環境構築はstackを使うものとcabalを使うものの2種類があります。
歴史的にはcabalの不便さを改善したのがstackですが、現在はcabalも進化して
不便さは大幅に解消されているため、筆者の好みでcabalによる環境構築をまとめます。

ツールのインストール

ghcup

ghcupはGHCとcabalとHLSをバージョン管理してくれるインストーラです。

インストールガイド を参考にインストールしてください。

バージョン確認

ghcupをインストールしたら

ghcup list

を実行しましょう。利用可能なツールのバージョンが表示されます。

$ ghcup list
   Tool  Version        Tags                      Notes      
✗  ghc   7.10.3         base-4.8.2.0                         
✗  ghc   8.0.2          base-4.9.1.0                         
✗  ghc   8.2.2          base-4.10.1.0                        
✗  ghc   8.4.1          base-4.11.0.0                        
✗  ghc   8.4.2          base-4.11.1.0                        
✗  ghc   8.4.3          base-4.11.1.0                        
✗  ghc   8.4.4          base-4.11.1.0                        
✗  ghc   8.6.1          base-4.12.0.0                        
✗  ghc   8.6.2          base-4.12.0.0                        
✓  ghc   8.6.3          base-4.12.0.0                        
✗  ghc   8.6.4          base-4.12.0.0             hls-powered
✓  ghc   8.6.5          base-4.12.0.0             hls-powered
✗  ghc   8.8.1          base-4.13.0.0                        
✓  ghc   8.8.2          base-4.13.0.0             hls-powered
✓  ghc   8.8.3          base-4.13.0.0             hls-powered
✔✔ ghc   8.8.4          recommended,base-4.13.0.0 hls-powered
✗  ghc   8.10.1         base-4.14.0.0             hls-powered
✗  ghc   8.10.2         base-4.14.1.0             hls-powered
✗  ghc   8.10.3         latest,base-4.14.1.0                 
✗  ghc   9.0.0.20200925 prerelease,base-4.15.0.0             
✗  cabal 2.4.1.0                                             
✗  cabal 3.0.0.0                                             
✔✔ cabal 3.2.0.0        latest,recommended                   
✗  cabal 3.4.0.0-rc4    prerelease                           
✓  hls   0.6.0                                    stray      
✔✔ hls   0.7.1          latest,recommended                   
✗  ghcup 0.1.12         latest,recommended    

各行の先頭の1文字目はツールがインストールされているか、2文字目は現在有効になっているかを表します。特に理由がなければrecommendedとなっているものを選びましょう。

ghcupが最新でない場合はアップグレードしておきましょう。

ghcup upgrade

ghcのインストール

GHCはコンパイラです。今回は8.4.4をインストールします。

ghcup install ghc 8.4.4
ghcup set ghc 8.8.4

cabalのインストール

cabalはパッケージマネージャです。今回は3.2をインストールします。

ghcup install cabal 3.2.0.0
ghcup set cabal 3.2.0.0  

hlsのインストール

hlsはわりと最近リリースされたLanguage Server (IDEエンジン)です。今回は0.7.1をインストールします。

ghcup install hls 0.7.1
ghcup set hls 0.7.1

プロジェクトの作成

Githubレポジトリの作成

最初にGithub上でレポジトリを作っておきます。

READMEと.gitignoreを作成しておくと良いです。

作成したらローカルにcloneしましょう。

git clone git@github.com:autotaker/start-haskell.git
cd start-haskell

cabalプロジェクトの初期化

プロジェクトの初期設定を行います。

cabal update
cabal init -i

二つ目のコマンドを実行すると対話的にプロジェクトの設定ができます。

いくつかポイントを押さえておくと

  • 最初の質問(Should I generate a simple project with sensible defaults?)はn
  • package type: Library and Executable
  • cabal spec: 2.4
  • Application source directory: app
  • Library source directory: src
  • Test source directory: test

にしておきましょう。それ以外はデフォルト値にしておけばとりあえずは問題ありません。

以下が設定例です。

$ cabal init -i
Should I generate a simple project with sensible defaults? [default: y] n
What does the package build:
   1) Executable
   2) Library
   3) Library and Executable
Your choice? 3
What is the main module of the executable:
 * 1) Main.hs (does not yet exist, but will be created)
   2) Main.lhs (does not yet exist, but will be created)
   3) Other (specify)
Your choice? [default: Main.hs (does not yet exist, but will be created)] 1
Please choose version of the Cabal specification to use:
 * 1) 1.10   (legacy)
   2) 2.0    (+ support for Backpack, internal sub-libs, '^>=' operator)
   3) 2.2    (+ support for 'common', 'elif', redundant commas, SPDX)
   4) 2.4    (+ support for '**' globbing)
Your choice? [default: 1.10   (legacy)] 4
Package name? [default: start-haskell] 
Package version? [default: 0.1.0.0] 
Please choose a license:
   1) GPL-2.0-only
   2) GPL-3.0-only
   3) LGPL-2.1-only
   4) LGPL-3.0-only
   5) AGPL-3.0-only
   6) BSD-2-Clause
 * 7) BSD-3-Clause
   8) MIT
   9) ISC
  10) MPL-2.0
  11) Apache-2.0
  12) LicenseRef-PublicDomain
  13) NONE
  14) Other (specify)
Your choice? [default: BSD-3-Clause] 
Author name? [default: autotaker] 
Maintainer email? [default: xxxxxx@xxxxx.com] 
Project homepage URL? https://github.com/autotaker/start-haskell
Project synopsis? my favorite cabal project example
Project category:
 * 1) (none)
   2) Codec
   3) Concurrency
   4) Control
   5) Data
   6) Database
   7) Development
   8) Distribution
   9) Game
  10) Graphics
  11) Language
  12) Math
  13) Network
  14) Sound
  15) System
  16) Testing
  17) Text
  18) Web
  19) Other (specify)
Your choice? [default: (none)] 
Application (Main.hs) directory:
 * 1) (none)
   2) src-exe
   3) app
   4) Other (specify)
Your choice? [default: (none)] 3
Library source directory:
 * 1) (none)
   2) src
   3) lib
   4) src-lib
   5) Other (specify)
Your choice? [default: (none)] 2
Should I generate a test suite for the library? [default: y] y
Test directory:
 * 1) test
   2) Other (specify)
Your choice? [default: test] 1
What base language is the package written in:
 * 1) Haskell2010
   2) Haskell98
   3) Other (specify)
Your choice? [default: Haskell2010] 
Add informative comments to each field in the cabal file (y/n)? [default: n] n

Guessing dependencies...

Generating LICENSE...
Generating Setup.hs...
Generating CHANGELOG.md...
Generating src/MyLib.hs...
Generating app/Main.hs...
Generating test/MyLibTest.hs...
Generating start-haskell.cabal...

You may want to edit the .cabal file and add a Description field.

さてプロジェクト構成を確認しておきましょう。

$ tree 
.
├── CHANGELOG.md
├── LICENSE
├── Setup.hs
├── app
│   └── Main.hs
├── src
│   └── MyLib.hs
├── start-haskell.cabal
└── test
    └── MyLibTest.hs

3 directories, 7 files

gitにコミットしてpushしましょう。

git add .
git commit -m 'init cabal project'
git push

CIの設定

人権を得るためにGithub Actionsを設定しておきましょう。
publicレポジトリなら無料です。

Actionsタブを開くとHaskellがsuggestされているはずなので、Set up this workflowボタンを押します。

ghcとcabalのバージョンを変更してコミットしましょう。

しばらく待つと、CIでのビルドとテストが実行されます。

CI badgeを作っておくとちょっとかっこいいです。

生成されたURLをREADME.mdに書いてコミットしましょう。

ビルド設定

ローカルでのビルドの設定を行います。

cabal configure --enable-coverage --enable-document --enable-profiling

すると cabal.project.localファイルが生成されます。
このファイルはローカルでのビルド時に使われるものなのでコミットはしなくて良いです。

profiling: True
coverage: True
library-coverage: True
documentation: True

package *
  documentation: True

説明しておくと、

  • profiling: Haskellのプロファイラを有効化します。少し実行が遅くなりますが、代わりにプロファイルが取れるのと、スタックトレースがまともになりデバッグしやすくなります。
  • coverage: 実行時にカバレッジが取れるようになります。
  • documentation: haddockを生成します。IDEでドキュメントを表示してくれるので便利です。

そのほかのオプションは こちらを参考にしてください。

IDE設定

さて次はIDEの設定を行いましょう。

プラグインインストール

VSCodeを入れて、Haskell extensionを入れます。

ワークスペースの設定

VSCodeでcabalプロジェクトのディレクトリを開き、ワークスペース設定を開き、

Text Editor > Format on saveを有効化します。

https://github.com/autotaker/start-haskell/commit/611ed45ea7c023447f2ac0c522e9f5d028fab555

タスクの設定

ビルドやテストをタスクとして追加します。

コマンドパレットから、Tasks: Configure Taskを選択するとtasks.jsonが作成されます。

cabal build, cabal testを登録しておきましょう。

https://github.com/autotaker/start-haskell/commit/aa542fab4f358309644448b759016e9cadcb6736

これでCtrl + Shift + B (MacならCmd + Shift + B)でビルドが走るようになります。

追加の設定

ここまでが基本的な設定でした。
ここからはちょっと開発が便利になる設定を紹介していきます。

Common stanza

cabalファイルではlibraryとtestそれぞれで依存ライブラリを記述する必要があります。
昔のcabalではベタ書きするよりほかはなかったので以下のように
ほぼ同じ設定を2回書く必要があって面倒でした。

library
  build-depends:
    -- long list of dependencies
   
test-suite start-haskell-test
  build-depends:
    -- long list of dependencies again 

しかし今のcabalは違います。common ${セクション名}で始まるセクションに共通の設定を書いて
各セクションの最初にimport: ${セクション名}を書くことで設定を継承することができるようになりました。

common shared-properties
  default-language: Haskell2010
  build-depends:
    -- list of common dependencies
    
library
  import: shared-properties

test-suite start-haskell-test
  import: shared-properties
  build-depends:
    -- list of test only dependencies

記載例は以下のコミットを確認ください。

https://github.com/autotaker/start-haskell/commit/224e8bf0052414acb473bfa498a40206d5c4d1c5

詳細はこちらを参照ください。

hspec-discover

hspecはHaskellユニットテストのデファクトスタンダードです。
hspec-discoverというツールを使うことでテストディレクトリ以下の
*Spec.hsにマッチするモジュールを探索して、各モジュールのspecを実行してくれます。

設定例は以下のコミットを参考にしてください。
https://github.com/autotaker/start-haskell/commit/c81d5aa8fcfe7f144b771d4d6b0a65bfa6f1c97e

cabal-fmt

cabalファイルにはexposed-modulesother-modulesにモジュールのリストをベタ書きしなければなりません。ファイルを追加するたびにcabalファイルを編集するのは面倒です。

この面倒な作業を自動化してくれるツールがcabal-fmtです。
このツールは.cabalファイルを整形してくれるフォーマッタですが、
ディレクトリを探索して、モジュールリストを自動更新してくれる機能があります。

インストール

ユーザのホームディレクトリにインストールしておくと良いです。

cd ~
cabal install cabal-fmt

設定

cabalファイルのexposed-modulesother-modulesの前に以下のようなコメントを挿入します。

-- cabal-fmt: expand ${ディレクトリ名}

expandの引数に-${モジュール名}をつけると、特定のモジュールを除外できます。

例えばhspec-discoverのSpecモジュールが追加されると困るので以下のように書きます。

-- cabal-fmt: expand test -Spec
other-modules: MyLibSpec

実行

-iオプションをつけると存在しているcabalファイルを上書きします。

cabal-fmt -i start-haskell.cabal

タスク設定

せっかくなのでVSCodeのtaskを追加して、ビルドやテストの実行前に自動実行されるようにしておきましょう。

tasks.json
@@ -3,6 +3,19 @@
     // for the documentation about the tasks.json format
     "version": "2.0.0",
     "tasks": [
+        {
+            "label": "cabal-fmt",
+            "type": "shell",
+            "command": "cabal-fmt -i start-haskell.cabal",
+            "presentation": {
+                "echo": true,
+                "reveal": "never",
+                "focus": false,
+                "panel": "shared",
+                "showReuseMessage": true,
+                "clear": false
+            }
+        },
         {
             "label": "build",
             "type": "shell",
@@ -19,7 +32,10 @@
             "group": {
                 "kind": "build",
                 "isDefault": true
-            }
+            },
+            "dependsOn": [
+                "cabal-fmt"
+            ]
         },
         {
             "label": "test",
@@ -37,7 +53,10 @@
             "group": {
                 "kind": "test",
                 "isDefault": true
-            }
-        }
+            },
+            "dependsOn": [
+                "cabal-fmt"
+            ]
+        },
     ]
 }
\ No newline at end of file

設定例は以下のコミットを参考にしてください。
https://github.com/autotaker/start-haskell/commit/2b04f8482f895fdf4ef1a00862acbec66c97650b

cradleの設定

VSCodeでHaskellのファイルを開くと以下のメッセージが出てきてうざいです。

これはhie.yamlというHLS向けの設定ファイルがないために表示されるメッセージです。

手で書いても良いのですが、自動生成してくれるツールがあるので使いましょう。

インストール

これもユーザのホームディレクトリにインストールしておきましょう。

cd ~
cabal install implicit-hie

hie.yaml生成

cabalファイルがあるディレクトリで以下を実行してください。

gen-hie > hie.yaml

すると以下のようなファイルが生成されます。

hie.yaml
cradle:
  cabal:
    - path: "src"
      component: "lib:start-haskell"

    - path: "app/Main.hs"
      component: "start-haskell:exe:start-haskell"

    - path: "test"
      component: "start-haskell:test:start-haskell-test"

これをコミットしておきましょう。

Warningの設定

おっと、Warningの設定をするのを忘れていました。

start-haskell.cabal
@@ -26,6 +26,7 @@ extra-source-files:
 common shared-properties
   build-depends:    base ^>=4.13.0.0
   default-language: Haskell2010
+  ghc-options:      -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wmissing-import-lists -Wcompat
 
 common test-depends
   build-depends:      hspec ^>=2.7.4

以下を参考に -Wcompatを追加しておきました。
(他にお勧めがあればコメントいただけると助かります。)

https://functor.tokyo/blog/2017-07-28-ghc-warnings-you-should-enable

まとめ

以上のようになかなか設定量は多いですが、色々と設定しておくと開発が捗ります。

以下のレポジトリに今回の設定をまとめました。参考にしてみてください。
https://github.com/autotaker/start-haskell

参考文献

https://haskell.e-bigmoon.com/posts/2019/10-07-cabal-fmt.html

https://github.com/mpickering/hie-bios#explicit-configuration

https://haskell.e-bigmoon.com/posts/2020/01-18-cabal-build-tool-depends.html

Discussion