この記事について
一、二ヵ月ほど前にHaskellに入門したばかりの著者です。そのためHaskellを学ぶためにいろいろな本を読んだりサイトを見たりして学んでいるのですが、どうもHaskellの文法や関数型、モナドについてばかりの情報がほとんどでプロジェクトを作成した後についての操作法がなかなか見つかりません。そこで少しの情報を基にいろいろ試行錯誤しました。それでわかってきたことを初心者ながらに説明しようと思い、これを書いています。そしてこの記事はHaskell (その4) Advent Calendar 2017の16日目の記事です。
プロジェクトの作成
まずプロジェクトを作ります。そのためにコマンドプロンプト上でプロジェクトを作りたいディレクトリの直下に移動してください。そこでstack new <projectname> [template]
コマンドを使います。(この記事では"<>"で囲まれたものを使用者毎に入力してもらうコマンドとし、"[]"で囲まれたものを省略可能なコマンドとして扱うことにします。)ただしprojectname
には作成したいプロジェクト名、template
は使用するテンプレートです。このテンプレートはstack templates
とすると確認することができるテンプレートの中から選択できます。デフォルトではsimple
を使用することになっています。今回はstack new testProject
コマンドによりプロジェクトを作成してこれを基に説明していきます。
コマンドの実行後、次のようなファイル群が作られているのが確認できます。
- testProject
- app
- Main.hs
- src
- Lib.hs
- test
- Spec.hs
- .gitignore
- ChangeLog.md
- LICENSE
- package.yaml
- README.md
- Setup.hs
- stack.yaml
- testProject.cabal
これらのうち.gitignore
、ChangeLog.md
の二つはgit
を使い始めたら出てくるようになり、package.yaml
は最近突然に生成されるようになっていたので出ない人もいるかも入れません。また~/sr/config.yaml
で
templates:
scm-init: git
params:
author-name: <Your Name>
author-email: <youremail@example.com>
category: <Your Projects Category>
copyright: <'Copyright (c) 2017 Your Name'>
github-username: <yourusername>
とここを参考に編集すると.git
ファイルも同時に生成してくれるようになりGitをすぐに使えるようになります。
生成されたファイルやフォルダの紹介
appフォルダ
このフォルダは実行用のファイルを入れておくところです。なので生成されていたMain.hs
は実行用のファイルです。実際にmain
関数が書いてあります。中身を確認してみます。
module Main where
import Lib
main :: IO ()
main = someFunc
まずMain
モジュールを宣言していますね。(なぜここでモジュールとして扱うように宣言しているのか著者にはわかりません)そしてsrc
フォルダにあるLib
モジュールを使うように宣言しています。(ここで注意したいのはLib.hs
ファイルを使うという宣言ではなくLib
モジュールを使うという宣言なのでsrc/Lib.hs
ファイルのことだと勘違いしないこと。著者は最初勘違いして少し泥沼にはまった。)このLib
モジュールはLib.hs
ファイルにて宣言されています。
次にmain
関数の型を宣言してから本体を宣言しています。Haskellには強力な型推論が備わっているので型の宣言は特に必要ないのですが、トップレベルだからあった方がいいということでmain
関数ですが宣言されているようです。そしてこのmain
関数で使うsomeFunc
の実装はLib
モジュールに書かれています。そのモジュールについて書いてあるLib.hs
ファイルで説明するのでここでは説明しません。
srcフォルダ
このフォルダはapp
フォルダ内で使う「道具」としての実装を記述したファイルを入れておくところです。主にモジュールを置くことになると思います。なのでLib.hs
のようにじかにモジュールとしてファイルが置いてあったり、新しくData
フォルダを作ってその下にモジュールを作って階層分けして置いてあったりして使われます。このようなことからmain
関数が書かれているファイルが存在しないのはすぐにわかります。(逆に考えるとmain
関数があるファイルだけでプロジェクトを作るならこのsrc
フォルダは使わないことになる。)
では、src
フォルダ内に生成されたLib.hs
ファイルを見てみましょう。
module Lib
( someFunc
) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"
まず私はLib
モジュールでsomeFunc
関数だけ外に見せると宣言しています。そしてsomeFunc
関数の型宣言をしたのち実装を定義しています。自動生成直後ではただ「someFunc」と表示するだけみたいです。
testフォルダ
このフォルダはプロジェクトのテスト関係のファイルを置くところです。著者がHaskellのテストについてまだ何もわからないので説明はできません。なので説明は省略させていただきます。
LICENSE / README.md / Setup.hs / stack.yamlファイル
LICENSE
、README.md
ファイルは名前の通りのファイルです。LICENSE
ファイルはある方法でどんなライセンスを使うか変更できるようです。(おそらく後で紹介するpackage.yaml
に記述するものかと思われる。)
Setup.hs
ファイルはstack new
コマンドによってどんなプロジェクトが作られたのかの情報が書かれています。stack.yaml
ファイルにはそのプロジェクト固有のオプションが書かれている(?)みたいです。正直Haskell初心者である著者には何もわかりませんが小さいプロジェクトを作って実行するまでいじる必要はないものだと思われるので無視しても構いません。
package.yaml / testProject.cabalファイル
package.yaml
ファイルは Hpack
(?)が使用可能なときに生成されるらしいファイルで、ビルド時にtestProject.cabal
を自動生成してくれるパッケージ管理ファイルです。どちらのファイルも「#」以降の同行の文字列はコメントになります。先に同じパッケージ管理ファイルのtestProject.cabal
について説明します。
testProject.cabalについて
正直前半はプロジェクトに関する説明なので、見れば何を意味しているのか分かるので省略します。このcabal
ファイルでは複数指定する場合は「,」で区切ります。
以下に後半のlibrary
からを表示します。
library
hs-source-dirs:
src
build-depends:
base >= 4.7 && < 5
exposed-modules:
Lib
default-language: Haskell2010
executable testProject-exe
main-is: Main.hs
hs-source-dirs:
app
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >= 4.7 && < 5
, testProject
default-language: Haskell2010
test-suite testProject-test
type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs:
test
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >= 4.7 && < 5
, testProject
default-language: Haskell2010
まずlibrary
に関しての説明をします。これはsrc
フォルダと関係しています。何を言ってるかわからないと思うので説明の為、上から見ていこうと思います。
まずhs-source-dirs
は自身の作成するHaskellのソースコードを置いておくところです。これによりsrc
フォルダが認識されその中のLib.hs
ファイルも認識できるようになります。
次にbuild-depends
についてです。これは以下でも何回も出てくる設定です。文字通り「ビルド時に依存するパッケージ」についての設定となります。今回はlibrary
での設定なのでsrc
の下にあるファイルが使うモジュールをここに記述していきます。例えばCotrol.Lens
を使うときはlens
を追記します。(ここで指定するのはパッケージ名)また、最初から入力されているbase <= 4.7 && < 5
はHaskellのどのベースパッケージを使うかを意味しています。これによりパッケージの依存関係の解消時に使用できるスナップショットの範囲が大体決まります。パッケージが古いものの場合base
があっていないこともあるかもしれないのでその時はHackageでパッケージを検索して確認してみてください。
次はexposed-modules
についてです。ここでは外部にさらされる(exposed)とき、すなわちapp
フォルダやsrc
フォルダ内のファイルが同フォルダ内のモジュールを使用するときに使いたいモジュール名を書きます。生成直後ではapp
フォルダ内のMain.hs
がLib
モジュールを使用するのでLib
が書かれています。
最後のdefault-language
についてですがHaskellの歴史上HaskellにはHaskell 1.0, Haskell 98, Haskell′, Haskell 2010の四つのバージョン名がついているのでどのHaskellを使うかについて書くところになります。著者の環境ではHasell 2010で生成されており最新ですね。
以下の説明ではかぶるところは適宜省略していきます。
次にexecutable
について説明します。基本同じですね。しかしlibrary
とは違いすぐ横にtestProject-exe
と書かれています。これはビルドしたときに出来上がる実行ファイル名を意味しています。つまりこのままビルドするとtestProject-exe.exe
(著者はWindowsを使っているのでexeファイルとしている。)が出来上がることになります。(いやなら好きなように変えてしまいましょう。)この関係は後で説明するtest-suite
とtestProject-test
でも(著者はHaskellのテスト関係は全然わからない為)同じだと思われます。
そしてmain-is
についてですが名前からするとmain
関数を持つファイル名の一覧か最初に実行するファイルの名前です。(著者は複数の実行用ファイルを使ったことがないのでわかりません。)しかし、初心者時点では単にmain
関数があるファイルの名前だと思っても差し支えなさそうです。そしてそのファイルが書かれているフォルダを指定するのが次のhs-source-dirs
です。
次のghc-options
ではstackでstack build
コマンドを実行時に使用するghcのオプションとして設定するオプションを指定できます。
次はbuild-depends
について説明します。ここに書くのはlibrary
で書くのと考え方は基本同じ(つまりapp
以下で使用する外部パッケージについて書く)ですが、一つだけ違います。それはtestProject
の記述です。これによりlibrary
で記述したexposed-modules
オプションにおけるモジュール(今でいうとLib
モジュール)が使用可能になります。
最後にtest-suite
について説明します。と言いたいところですが、type
以外は他と同じようなものであり、その唯一違うtype
について著者はわからないので説明できません。
package.yaml について
testProject.cabal
を自動生成する似たような書き方の package.yaml
で、実際に書き方も似ています。ただ区切りが「,」ではなく「-」となることには注意しておいてください。
pacage.yaml
ファイルもtestProject.cabal
ファイル同様に前半はそのプロジェクトの情報なので省略し、後半だけ説明します。
後半を以下に表示します。
dependencies:
- base >= 4.7 && < 5
library:
source-dirs: src
executables:
testProject-exe:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- testProject
tests:
testProject-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- testProject
見てすぐにわかるようにexecutables
、tests
の内容はtestProject.cabal
と同じです。また、名前が複数形になっておりインデントの深さからわかるように同様にtestProject-○○
以下をそれぞれインデントの深さに気をつけて下につなげていけば複数いけるみたいです。(情報としては聞いたことがあるが著者はやったことがないため。)
さて、library
においてexposed-modules
オプションがなくなっていることにお気づきでしょうか。これは自動でlibrary
のsource-dirs
から探してくれるので不要になったようです。なお、同じく消えているlibrary
のdependencies
は、自動生成時には生成されたプロジェクトのファイルの都合上その時には必要ないので削除されているだけ見たいです。なので自分で新しくオプションとして書いても問題がないようです。(その証拠に下に記したビルドの例がある。)
最後に説明の都合上順番が逆になってしまいましたが、一番上にあるdependencies
について説明します。これはlibrary
、execuables
、tests
で共通して依存するものを書くときに使われます。自動生成時にはtestProject.cabal
でも何回も書かれていたbase
について書いてあることからもわかるかと思います。
プロジェクトのビルド
普通にビルドするだけではつまらない(新しめのstackでstack build
をすれば問題なく終わるため。)ので、少し編集してビルドしてみます。今回は以下のように編集してみることにしました。
{-# LANGUAGE TemplateHaskell #-}
module Lib
( someFunc
) where
import Control.Lens
someFunc :: IO ()
someFunc = putStrLn "someFunc"
標準に入っていないLens
パッケージを使ってみることにしました。
まずこの状態でstack build
とコマンドプロンプトに入力してみましょう。多分コンパイラに「Control.Lens
て何?知らないよ?」と怒られるでしょう。なので、stack install lens
としてパッケージlensをインストールしておきます。するとそのプロジェクト内でlensパッケージが使えるようになります。(ここでlensパッケージとしたのはControl.Lens
モジュールが所属するパッケージ名がlensであるため(hackage参照))lensパッケージは大きいパッケージなので少し時間がかかるので少し待ちましょう。
インストール終了後、もう一度stack build
してみましょう。すると先とは違ったエラーが出てきます。そこには「そのControl.lens
モジュールが書かれたパッケージ見つかったはいいがtestProject.cabal
ファイルに記述されていない。 」というようなエラーのようです。ならば記述してやりましょう。今回は著者の環境ではtestProject.cabal
が自動生成されて効果を確認できないためpackage.yaml
に書きます。今回はsrc
フォルダ内でしか使っていないのでlibrary
オプションに書き、以下となりました。(dependencies
に- lens
を追記しても問題なし。)このとき「-」の後には空白を開けておかないとパースエラーになってしまうので注意してください。
library:
source-dirs: src
dependencies:
- lens
今回の例の作成時には著者の環境でstack build
したときにlens-4.15.4
がほしいといわれていました。ですが、package.yaml
が問題が起きないように自動的にバージョンを指定してくれるのでバージョン指定をしなくても大丈夫です。
そして保存してからもう一度stack build
すると今度は問題なくビルドがとおって実行ファイルができます。
実はstack install lens
コマンドを使わなくてもいきなりpackage.yaml
に記述するとビルド時にインストールしてからビルドをしてくれます。便利です。
作成した実行ファイルを実行する
ビルドしたファイルは生成されている.stack-work
フォルダ内の深いところにあります。これはビルド時のメッセージを読めば場所はすぐにわかるのですが、いちいち探すのもめんどくさいのでstackには実行に関する二つのコマンドがあります。ここではそのコマンドを説明します。
実行に関係するコマンドは二つあります。stac exec <fiileName>
とstack install <projectName>
の二種類です。(余談 : 実行ファイルがもしファイルを作成したらファイルがどこにできるか、まだわからないのでわかる人教えてください)
stac exec <fiileName>
その場でfileName
から始まる実行ファイルを実行します。今回説明時に変更した以外のところを何も手を加えなければstack exec testProject-exe
でsomeFunc
と表示されます。
stack install <projectName>
stack install testProject
とコマンドを入力すると.stack-work
フォルダ内からプロジェクトの成果物がインストールされます。場所はコマンドを実行したときに表示されるのですが、決まって同じところのようなのでパスを通しておくと、コマンド(例えばtestProject-exe.exe
)ですぐに実行できるのでよさそうです。
さて、コピー先のフォルダを見てみるとtestProject-exe
を名前に持つ実行ファイルが見つかります。なのでそれを実行してみましょう。(ダブルクリックや、コマンドプロンプト上でtestProject-exe.exe
と打つかドラッグアンドドロップしてエンターして実行させましょう。)すると問題なくsomeFunc
と表示されます。
締めの言葉
以上で説明は終わりになります。だらだら長いわかりにくい文章を最後まで読んでいただきありがとうございました。以下は雑談のような蛇足のようなものなので読んでいただかなくても構いません。
雑談のような何か
ごく最近Haskellに入門したばかりでCabalヘルなどを知らないし、Haskellの開発環境事情やGUI事情、テスト事情なども全くわからない著者です。(教えていただけるとありがたいです。)そんな著者ですが、この記事を書こうと思った原因があります。それはtwitterで「Haskellを広めたいならstack等の周辺ツールについての説明を増やすべき」と発言したことです。これが著者が思っていたよりも多くの賛同が得られてしまいました。なので「他人に命令するなら自分も何かしろ」ってことでこの記事を初心者ながら書いてみました。Haskellを広めたいと思っている人は案外こういったHaskellの周辺の方が需要あるんだよってことを頭の片隅に置いておいてくださるとありがたいです。共感した方はどうか周辺ツールについての説明をお願いします。