Haskellでコマンドラインアプリケーションを作る時の基本的な情報とTips
Haskellでコマンドラインアプリケーション(以下CLI)を作る時の基本的な情報とTipsをまとめてみました。 Haskellは雰囲気で読める、しかしCLIはあまり作ったことが無い、って人が想定読者です。
この記事はHaskell Advent Calendar 2014の16日目のエントリです。
とりあえず何のサンプルコードも無く話を進めるのも雰囲気が伝わらないかなと思って、Gitリポジトリにあるファイルの中身を標準出力に出力するプログラムをls-more
という名前で作ってみました。
module Main where import Control.Monad import Data.Monoid import qualified Data.Version import Options.Applicative import Paths_ls_more (version) import System.IO (Handle, hGetLine, hIsEOF) import System.Process (runInteractiveProcess) data CommandLineOption = CommandLineOption { showVersion :: Bool -- バージョンを出力 , show3Lines :: Bool -- 3行だけ出力 } commandLineOption :: Parser CommandLineOption commandLineOption = CommandLineOption <$> switch ( long "verion" <> short 'v' <> help "Show version" ) <*> switch ( long "show-3-lines" <> short '3' <> help "Show 3 lines only" ) main :: IO () main = do -- コマンドラインオプションパーサーを実行してオプションを取得 opts <- execParser (info commandLineOption mempty) -- バージョンを出すか、実行するかで分岐 if showVersion opts -- バージョンを出す then putStrLn $ Data.Version.showVersion version else do -- `runInteractiveProcess`で実行した外部プロセスの標準出力を受け取る (_,out,_,_) <- runInteractiveProcess "git" ["ls-files"] Nothing Nothing -- 本処理へ go opts out where go :: CommandLineOption -> Handle -> IO () go opts handle = do -- ファイル末尾か判定 eof <- hIsEOF handle if eof then return () else do -- 一行読む path <- hGetLine handle --- ファイルの中身を読む content <- readFile path -- ファイルパスを出力 putStrLn path if show3Lines opts then -- 3行だけ出力 forM_ (take 3 (lines content)) putStrLn else -- すべて出力 putStrLn content -- 次へ go opts handle
いかがでしょうか。冒頭にこのサンプルコードを置く意味がどの程度あったのかという問いを放置しつつ、箇条書きでCLIを作る際に必要な情報をまとめていこうと思います。
ファイルの読み書き
Prelude
にreadFile
、writeFile
など基本的な関数があります。ByteString
、Text
向けにも同じインターフェイスの関数が用意されています。
- http://hackage.haskell.org/package/base/docs/Prelude.html
- http://hackage.haskell.org/package/bytestring/docs/Data-ByteString.html
- http://hackage.haskell.org/package/bytestring/docs/Data-ByteString-Lazy.html
- http://hackage.haskell.org/package/text/docs/Data-Text-IO.html
- http://hackage.haskell.org/package/text/docs/Data-Text-Lazy-IO.html
ディレクトリ操作
System.Directory
を使います。
ファイルパス
System.FilePath
を使います。
ドキュメントはSystem.FilePath.Posix
とSystem.FilePath.Windows
に分かれていますがインターフェイスは同じです。
- http://hackage.haskell.org/package/filepath/docs/System-FilePath-Posix.html
- http://hackage.haskell.org/package/filepath/docs/System-FilePath-Windows.html
外部プロセスの呼び出し
System.Process
を使います。readProcess
、readProcessWithExitCode
を使うことが多いような気がします。
結構前の話ですがsystem
が1.2.0.0で非推奨になってしまいました。よく使う関数だったので衝撃です。今後はcallCommand
を使えば良いみたいです。
文字列操作
真面目にHaskellのプログラムを書いていると、ユニコードの文字を出力する必要がある、遅い、などの事情で素のString
ではなくByteString
とText
を使うことになると思います。「ファイルの読み書き」にも書きましたが、この2つのライブラリははPreludeと同じインターフェイスの関数を多く装備していて、それを使うとByteString
/Text
の文字列もリスト操作感覚でいじることができます。
終わり方
exitSucess
/ exitFailure
が System.Exit
にあります。
標準入出力
getChar
、getLine
、getContents
など基本的なものはPrelude
にあります。さっきのサンプルプログラムにあるようにHandle
を指定するなどの場合はSystem.IO
にあるh
で始まるものを使います。
これもByteString
/Text
に同様の関数があります。
- http://hackage.haskell.org/package/base/docs/Prelude.html
- http://hackage.haskell.org/package/bytestring/docs/Data-ByteString.html
- http://hackage.haskell.org/package/bytestring/docs/Data-ByteString-Lazy.html
- http://hackage.haskell.org/package/text/docs/Data-Text-IO.html
- http://hackage.haskell.org/package/text/docs/Data-Text-Lazy-IO.html
今回は省略を省きますが、conduit
、pipes
、io-streams
等のストリーム処理ライブラリを使うのも良いと思います。
- http://hackage.haskell.org/package/conduit
- http://hackage.haskell.org/package/pipes
- http://hackage.haskell.org/package/io-streams
コマンドラインオプションは?
沢山ライブラリがあります。optparse-applicative
がおすすめです。APIの変更が若干激しい、ドキュメントが読みづらい等欠点はありますが使いやすいライブラリです。
- https://www.haskell.org/haskellwiki/Command_line_option_parsers
- https://github.com/pcapriotti/optparse-applicative
同梱したファイルを使いたい
.cabal
ファイルのdata-files
プロパティで同梱するファイルを指定できます。
このようなパッケージ固有の情報はPaths_pkgname
というモジュールから読めます。同梱ファイルのパスはgetDataFileName
で取得できます。
バージョンを出したい
サンプルコードにある通り、
import Paths_pkgname import Data.Verison (showVersion)
して、 showVersion Paths_pkgname.version
で取得できます。
設計
個人的には下記のような構造に落ち着きました。各工程をテストできるのが良いです。 実際はもうちょっとグチャグチャになります。
-- コマンドラインオプションを生成 parseArgs :: [String] -> CommandLineOption -- コマンドラインオプションからオプションを生成。 -- 環境変数などはここで読んで、オプションを完成させる buildOption :: CommandLineOption -> IO Option -- オプションから処理を実行。 run :: Option -> IO () -- 各部品をつなげる。 main = (parseArgs <$> getArgs) >>= buildOption >>= run
おわりに
HaskellでCLI作るの楽しいです。相性良い気がします。是非お試しを!
追記
バージョン情報が無かった。各ライブラリは2014/12/16時点で最新のものを使っています。
$ ghc --version The Glorious Glasgow Haskell Compilation System, version 7.8.3 $ cabal --version cabal-install version 1.20.0.3 using version 1.20.0.2 of the Cabal library
Tools for trade Late 2014
OS and Application
- MacBook Pro 15inch
- Mac OS X
- Terminal.app
- MacVim.app
- Divvy.app
- Karabiner.app
- LICECap.app
- Writer Pro
- Spotlight as a launcher
CLI
Zsh
- Colorful prompt
- Path and Git branch on the right
- Cabal sandbox status
- Lots of one character aliases
Vim
- vimrc is about 250 kloc
- ctrlp, syntastic, gitgutter, vim-align
- Just omni complete
- Wrap parens
- Emacs keybind in insert mode
- Warnings for whitespace etc.
- Search with /
Git
$ g
forgit
$ s
forgit status --branch --short
$ d
forgit diff
- Commit is always verbose to write good commit comment
$ git fixup
to amend staged changes to previous commit- Heavy user of
git-grep
with these options
Custom defined Git commands
$ git tb
to create topic branch named by last commit$ git gsub
for substitution. I can't write code without this now
Repository management
$ r (repo url)
to clone a repository$ r (repo name)
to move to the cloned repository$ r \TAB
to auto complete repositories$ r \ENTER
to intaractive selection of repository
機内持ち込みオンリーで海外旅行する人の荷物Tips
出張で一週間フィリピンに行きました。ここ数年、毎年一週間ほど海外に行ってるんですが、常に機内持ち込みのバックパックのみです。そこら辺のノウハウが溜まってきたので共有します。
そもそもなぜ機内持ち込みのみで頑張ってるのか?
- どのサイズのスーツケースを買うか悩みすぎている
- スーツケース無しでなんとかなってるし、今回も買わないでいいか、という結論に達してしまう
- 荷物待ち無しは気分が良い
- 荷物が少ないのも気分が良い
荷物
バッグ
これです。35L。
特に不満は無い。PCも入るし。普段から使ってる。35Lが機内持ち込みの限界サイズだと思う。縦に長いと持ち込めないので気をつけて。
衣類
仕分けケースに入れる。それを圧縮袋で圧縮する。ZiplocでもOK。汚れた衣類はZiplocに入れて、圧縮する。そのために空のZiplocを数枚持っていく。仕分けケースは無印にいい感じの色のがある。Ziplocはフリーザーパックのほうが強い気がする。
下着は速乾のもの。ホテルのバスルームで一日で乾きます。 綿100%の洋服は着ない。なぜなら、乾かないから。デニムとか履きたくなるけど諦める。Tシャツも綿ポリのを選ぶ。シャツは必要がなければ持っていかない。
靴はお気に入りを履いて行くとテンション上がって良いです。サンダルはゴミ袋に入れて持参する。ゴミ袋は多めに持ってると何かと便利。
本
フィジカルな本が必要な場合はフライト中の時間を潰せそうな量だけ持っていく。もし暇になったら…とか考えちゃダメ。そんな事は起こらない。旅先で気合いを入れて読書する予定があるなら別だけど(今回はあったのでこれを持っていった)。軽めの純文学がページあたりの消費時間量も多いのでオススメ。Kindleに読みたい本があるならそれがベスト。
今回はこれでした。面白かった。Los Pepesの話とか出てこなかったな…。
紙
ホテル・フライトの予約はプリントアウトしておく。直前に家にプリンター無くて困るってパターンが多いので、早めにやっておくべし。パスポートのコピーもしておく。
パスポート
空港ではチケットと旅程のプリントアウトを挟んでポケットに直接入れる。それ以外はコピーを持ち歩く。
お金
ちょっとだけ両替しておいて、あとは現地のATMでキャッシングする。
個人用の名刺
常に持ち歩く。あらゆるところで。予期せぬ出会いがあるので。
サングラス
日本より日差しが強い国はけっこう多いのでマスト。
財布とかを入れる小さいバッグ
これは今回の反省。水着にポケットがなくて財布・携帯・鍵が入れられませんでした…。 こういうので良いんじゃないかなと。
化粧水
空港の無印良品で小さい化粧水を買って持って行ってます。全身に使う。ホテルの石鹸が強い時に役に立つ。日焼けした時も使える。
もう一つの財布
国内用と別のを持っていくと詰め替えが楽。激安のを買う。
耳栓
安眠の友。フライトもだし、ホテルがうるさい場合もあるので。カナル型イヤフォンがあっても、音楽を聞きたくない時もあるので、別途持っていたほうがよい。空港で買えます。
アイマスク
フライトが長い場合はあると良い。耳栓とアイマスクして入力を減らすとかなりリラックスできます。空港で買える。
RSpec実行にまつわる小ネタとDSLについてのボヤキ
変更されたファイルのみ実行
$ bundle exec rspec `git diff --name-only`
インタラクティブに対象を選んで実行
peco便利。
$ bundle exec rspec `git ls-files | peco`
そういえば、最近GuardやListenで自動実行しなくなってしまった。理由は特にない。
RSpecの変化が速すぎてついていけない。常にRubyを書いているわけではないので(JavaScriptが多くて常にRubyを書いているわけではないので、rspec-mocks
も含めると気がつけばAPIが変わっていて、毎回ググっている。APIが安定しないライブラリは辛い。
そもそも、凝ったDSLを覚える事自体が辛い。特定のDSLの理解は再利用性が低いのが良くない。覚えても、そのライブラリ・フレームワークを使えるようになるだけという応用の効かなさが悲しい。逆に言語仕様への理解は再利用性が高い。当たり前だけど。書かれたコードをより深く理解できるし、自分で書くにあたっても、その表現力を活かせる。
ということで、凝ったDSLの学習は最小限にしたいなー、と思っております。
『パーフェクト Ruby on Rails』は本番Railsアプリの完成度UPに良いと思う
"Startup Java"と揶揄されるくらい定着したウェブアプリケーションフレームワーク、Ruby on Rails。情熱的なRubyコミュニティ、スタートアップ業界中心の圧倒的な需要が合わさって、巨大なエコシステムが形成されました。チュートリアルや入門書も充実して、すっかり始めやすいフレームワークになった感じです。
けど、プロダクション・クオリティのものを作るに当たって完成度を上げていくための情報って案外まとまってなかった印象があります。例外通知とか、CIの整備とか、プロビジョニングとか、モデルを整理する方法とか。『パーフェクト Ruby on Rails』はそこら辺がカチッとまとまってます。 ガチRailsエンジニアのいないスタートアップでコードの品質あげる、とか、ぴったりだと思う。
あと、RubyもWeb開発もある程度わかるんだけどRailsが巨大過ぎて手を出せてない人。Railsの概要、Rails的なMVC、実際のアプリケーション開発の流れ、周辺ツールがギチギチっとまとまってるので、全容を把握するのにはちょうどよいのではないか。
あと9章。昔こんな事をつぶやいていたのを思い出した。
今後、私はRailsで作るあらゆるフォームにActiveModelを使うことを誓います。
— fujimura (@ffu_) January 17, 2012
これまじです。非ActiveRecordなフォームを作る時は絶対やったほうがいい。最近はActiveModel::Model
があるから楽なはず。
Service Classについては、意味的にしっくり来る名前が見つかった or 自分以外のModelに副作用を起こし始めた場合に切り出す、ってのが個人的なルールかなあ。リソース名に収まるまでよい抽象化(要はネーミングですが)を諦めない、とか。
って感じで、とても楽しく読ませて頂きました。@udzura さん献本ありがとうございました!
トレードオフの罠
デザイナーとかプログラマーという職業の特殊なところは、創作活動に近い面を含んでいる、ということだと思う。完全にその言葉が相応しいとは思わないけど、アーティストに近い面があるとも言える。実際、高い完成度や生産性を目指して、単なる仕事の枠を超えた情熱をもって手を動かし続けている人がけっこうな数いる。そして、それが往々にして本人や組織の強みの源になっていたりもする。
実際の事業の中では、クオリティと時間・人・予算の間のトレードオフが常に発生する。あくまで事業としては、製品の品質はそれ事業の中で果たす役割を全う出来る程度であれば良い。専門家として目指しているクオリティに達する必要がない場合もある。グリッドが多少狂ってても、やたらアドホックな実装が残ってても、必要な役割を果たせていれば十分なのである。
ここにジレンマがある。トレードオフだ何だと言ってクオリティに妥協し続けていると、そのうちクオリティに対する感度が麻痺してくる。何が良いものなのかわからなくなって、良い物を作れなくなってしまう。麻痺するとそこそこの物を作るのが上手になるかというとそんな事もないので(理想がないとトレードオフはできない)結果的にアウトプット全体の平均点が下がっていってしまう。
うーん。これはどうすればいいのか。