歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Haskell】optparse-genericを使ってコマンドラインツールを作るまで

Haskellは優れた型システムによって見通しの良いプログラミングができるものの、アプリケーションを作る際にはどうしても敷居が高く感じてしまっていました。

というのも、PythonRubyなどのスクリプト言語では豊富なパッケージの組み合わせで小さなプログラムならサクッと作れてしまうことを考えると、自分にとって新しい言語をわざわざ使うメリットを考えてしまい、単なる教養やお勉強の領域だけにとどまっていました。

著名で使い勝手の良いライブラリを調べる

Haskellの"package archive"であるHackageには多数のパッケージが登録されているものの、この中のどれを使えばいいのか、なかなか候補が絞り込めずにいました。

翻ってPythonについて考えると、私はまずawesome-pythonリポジトリを参考にし、ここに自分の欲しい機能を見て(「コマンドライン引数をパースするライブラリが欲しいからcommand-line-toolsの項目を見よう!」)、紹介されている中から自分にとって使いやすそうなものを選びます(「clickがシンプルで良さそう」)。それで良い物がなければPyPIを直接検索します。

同様にawesom-haskellというリポジトリがあれば、そこから調べていけば助かります。実際にGithub上に2つ存在しました。

こちらのリポジトリでは1000件以上のスターが付いており、Pythonのものと同様、各項目に整理されています。

github.com

もう一つのほうは整理されていないものの、上記のリポジトリで紹介されていないパッケージもあります。例えばコマンドラインパーサーもいくつか紹介されています。

github.com

この中から、使い勝手の良さそうなoptparse-genericというパッケージを使ってみます。

github.com

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

さらに詳しく知りたい方は、以下の記事を読むと良いと思います。

qiita.com

最後に

おおまかな開発フローは確認できたので、サンプルコードではなく実際のコマンドラインツールを今後作りたいと思います。

こんなの作ろうと考えています。