Haskellは優れた型システムによって見通しの良いプログラミングができるものの、アプリケーションを作る際にはどうしても敷居が高く感じてしまっていました。
というのも、PythonやRubyなどのスクリプト言語では豊富なパッケージの組み合わせで小さなプログラムならサクッと作れてしまうことを考えると、自分にとって新しい言語をわざわざ使うメリットを考えてしまい、単なる教養やお勉強の領域だけにとどまっていました。
著名で使い勝手の良いライブラリを調べる
Haskellの"package archive"であるHackageには多数のパッケージが登録されているものの、この中のどれを使えばいいのか、なかなか候補が絞り込めずにいました。
翻ってPythonについて考えると、私はまずawesome-pythonのリポジトリを参考にし、ここに自分の欲しい機能を見て(「コマンドライン引数をパースするライブラリが欲しいからcommand-line-toolsの項目を見よう!」)、紹介されている中から自分にとって使いやすそうなものを選びます(「clickがシンプルで良さそう」)。それで良い物がなければPyPIを直接検索します。
同様にawesom-haskellというリポジトリがあれば、そこから調べていけば助かります。実際にGithub上に2つ存在しました。
こちらのリポジトリでは1000件以上のスターが付いており、Pythonのものと同様、各項目に整理されています。
もう一つのほうは整理されていないものの、上記のリポジトリで紹介されていないパッケージもあります。例えばコマンドラインパーサーもいくつか紹介されています。
この中から、使い勝手の良さそうなoptparse-genericというパッケージを使ってみます。
stackを使って開発してみる
Haskellのビルドツールであるstackを利用して開発してみます。
stackが何かという話は、Rubyのプログラマーは「Haskell Stack でライブラリを安定させる | Netsphere Laboratories」を読めば分かると思います。
基本的なコンセプトは Ruby のための Bundler と同じ。プロジェクトが直接必要とするライブラリと, さらに依存するライブラリについて、システムグローバルにインストールされたパッケージで不足する場合, プロジェクト内にインストールしてしまう。
新規プロジェクト作成
開発用のディレクトリで新規プロジェクトを作成します。
stack new haskell-cli
ディレクトリ構成はこんなのが出てきます。
$ cd haskell-cli; tree . ├── LICENSE ├── README.md ├── Setup.hs ├── app │ └── Main.hs ├── haskell-cli.cabal ├── src │ └── Lib.hs ├── stack.yaml └── test └── Spec.hs 3 directories, 8 files
依存ライブラリの追加
(プロジェクト名).cabal
のファイルを編集します。本来は他の項目(ホームページのアドレス等)も変更する必要があるのですが、とりあえず動かすだけなのでこれだけで。
executable haskell-cli-exe hs-source-dirs: app main-is: Main.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base , haskell-cli , optparse-generic -- この行を追加 default-language: Haskell2010
サンプルコードをapp/Main.hs
に書き写します。
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} import Options.Generic data Example = Example { foo :: Int, bar :: Double } deriving (Generic, Show) instance ParseRecord Example main = do x <- getRecord "Test program" print (x :: Example)
ビルドして使ってみる
プロジェクトのディレクトリで実行すればビルドされます。
stack build
また、stack exec
コマンドでビルドしたものを使うことができます。
$ stack exec haskell-cli-exe -- --foo 1 --bar 2.5 Example {foo = 1, bar = 2.5}
ビルド先のディレクトリに移動すれば、直接実行することもできます。
$./haskell-cli-exe --foo 1 --bar 2.5 Example {foo = 1, bar = 2.5} $ # 引数が足りないとエラーを出してくれる $ ./haskell-cli-exe --foo 1 Missing: --bar DOUBLE Usage: haskell-cli-exe --foo INT --bar DOUBLE
さらに詳しく知りたい方は、以下の記事を読むと良いと思います。
最後に
おおまかな開発フローは確認できたので、サンプルコードではなく実際のコマンドラインツールを今後作りたいと思います。
こんなの作ろうと考えています。
自分の仕事のタスクの依存関係をワークフロー(DigDagとかAirflowとか)みたいなタスクのグラフ形式(?)で表現する方法を考えてたけど、「このタスクより先に終わらせておかないといけないタスク」のデータだけ用意してHaskellの順序集合の型クラスを使えば簡単かもしれない
— 黒めだか (@takeshi0406) 2017年5月14日
Task型を用意すると、「このTaskを先にやっておかなければいけない」って関係は、Ord型クラスのインスタンスにして順序集合で表現するのが多分自然なんだ(そして全順序集合じゃないはす)
— 黒めだか (@takeshi0406) 2017年5月14日