Haskellでいってみよう

日曜プログラマにも満たないレベルでもHaskellで何かソフトウェアを作りたい!

簡易cat - コマンドライン引数

はじめに、このブログはHaskellの入門記事でも何でもないので、その辺りはご期待には添えないのであらかじめお断りしておくことにする。単に、私がHaskellで何かしらソフトウェアを作るということのモチベーションを維持するためであり、またその過程を記録するものである。記述が大雑把なのもご容赦願いたい。ただせっかく稚拙で恥ずかしいソースを晒すので、願わくば「高位のHaskell使い」の方からHaskellらしからぬところ、より的確な関数/ライブラリなど、改善につながるご助言をいただければ幸いである。

さて、実用的なアプリケーションを作るにはコマンドライン引数を扱えないと話にならない(と考えた)ので、その辺を確認しておく。サンプルとして超簡易的なcatコマンドを作成する。

STEP 1

引数に指定したファイルを単に表示するだけのcatを作ってみる。まずは、最初の引数のファイルだけを表示する仕様とする。二番目以降の引数は無視。ちなみに、これではcatとは名ばかりである…。

Cat1.hs
module Main (
  main
) where

import System.Environment

main :: IO ()
main = do
  (x:xs) <- getArgs
  putFile x

putFile :: String -> IO ()
putFile f = do
  cs <- readFile f
  putStr cs

実行してみる。

$ ghc -o cat1 Cat1.hs
$ ./cat1 Cat1.hs
module Main (
  :
  putStr cs

一応動いた。getArgsはリストを返すので最初の要素だけを取り出してみた。ファイルの読み出しと標準出力への書き出しはとても簡単。readFileで読んでputStrするだけ。ちなみにこの辺のテキスト処理的な簡易コマンドの書き方については、この本を参考にした。関数プログラミングの解説書として云々という評価はあるが、私的にはHaskellで実用アプリを作るという事ではなかなかいいアプローチで説明もわかりやすいと思う。ただしAmazonではもう新品は売ってないのかな?

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

STEP 2

次に複数のファイル名を与えてそれらを順に表示させる事にする。これで一応catを名乗ることができる? 複数のファイル名はリストで渡されるので、リストの各要素に対し同じ処理を実行するには map というのがいいらしい。なので先ほどのプログラムにちょっとmapを付けてみる。

Cat2.hs
module Main (
  main
) where

import System.Environment

main :: IO ()
main = do
  xs <- getArgs
  map putFile xs

putFile :: String -> IO ()
putFile f = do
  cs <- readFile f
  putStr cs

コンパイルすると…

$ ghc -o cat2 Cat2a
  :
Cat2a.hs:14:3:
    Couldn't match type ‘[]’ with ‘IO’
    Expected type: IO ()
      Actual type: [()]
  :

怒られた。確かに map の型は「(a -> b) -> [a] -> [b]」なのでmainの型と合わない。さすがに"純粋な" map をここで使う事に無理があるのか?あきらめて再帰関数に変えてみた。

Cat3.hs
module Main (
  main
) where

import System.Environment

main :: IO ()
main = do
  xs <- getArgs
  putFiles xs

putFiles :: [String] -> IO ()
putFiles [] = do
  putStr ""
putFiles (x:xs) = do
  putFile x
  putFiles xs

putFile :: String -> IO ()
putFile f = do
  cs <- readFile f
  putStr cs

うまく行った!(実行結果は割愛)

ちなみに最初は再帰の底(putFiles [] = do のところ)を書いていなかったので実行時に怒られた。この辺、慣れが必要か。でも 'putStr ""' という処理が適切かどうかはわからない。

続きは次回。