入出力と do 記法

プログラムと言うからには入力と出力を扱えなきゃいけない。でないと何の役にも立たないからな。
ただ,純粋関数型言語の Haskell にとってはこの入出力というのは特殊なもののようだ。

  • 入出力は手続き的にならざるを得ない。なぜなら出力が入力よりも先に行えるはずがないから。だから,いつ,どんな順で評価しても結果が変わらない他の関数とはちがう。
  • 副作用がある。入出力をする関数を評価すると,評価の結果として値が返ってくるのとはべつに,入力を受け取ったり,画面に何かを出力したりということが起こる。これを副作用という。
  • 評価するたびに結果が異なる。たとえば Ruby では gets で標準入力から値を得ることができるけど実行するたびに違う値が返ってくる。Haskell ではこれはまずい。

Haskell ではこのような問題を避けるために特別な仕組みがある。それが do 記法。
口(?)で説明するのは難しいので例を示す。このプログラムはコマンドラインから受け取ったパラメータを標準出力に出力する。ようするに echo だ。

import System

main = do args <- getArgs
          putStr $ unlines args

この do 記法の中には2つの動作 (action) がある。 args <- getArgs でコマンドラインパラメータを受け取ってargsに代入する(空白で区切られて文字列のリストになる)。そして putStr $ unlines args で標準出力へ1行にひとつずつ出力する。do 記法の中の action は順番に実行される。 この様に do 記法の中ではプログラムの他の部分と違って手続き的に書くことができる。というか,(Haskellにとって)異質なものを他の部分から分離している,という方がいいだろうか。 もう少し詳しく言うと,do記法はべつに入出力のためだけのものではなくて,モナドという仕組みなんだけど,このモナド,なんだかよくわからないのでここでは脇に置いておく。とにかく,標準入出力やファイルを使おうとしたら do 記法を使えばいいってことだ。 上のプログラムをコンパイルして実行するとこうなる。あ,import System は getArgs を使うのに必要。

>type hecho.hs
import System

main = do args <- getArgs
          putStr $ unlines args

>ghc -o hecho hecho.hs

>hecho abc def ghi
abc
def
ghi

OK。これでプログラムが書けるぞ。

uniq

入出力の練習(その2)。

import System

main = do args <- getArgs
          mapM_ uniqFile args

uniqFile fileName = do contents <- readFile fileName
                       putStr $ unlines $ uniq $ lines contents

uniq [] = []
uniq (c:cs) = uniq' c cs
  where uniq' str [] = [str]
        uniq' str (c:cs) 
            | str == c = uniq' str cs
            | otherwise = [str] ++ uniq' c cs

結果。

>cat sample.txt
Perl
Perl
Ruby
Ruby
Ruby
Javascript
VBA
Haskell
Haskell
>uniq sample.txt
Perl
Ruby
Javascript
VBA
Haskell

cat

入出力の練習。ファイルの内容を表示する。

import System

main = do args <- getArgs
          mapM_ catFile args

catFile fileName = do contents <- readFile fileName
                      putStr $ contents

コンパイルして実行。

>cat sample.txt
FORTRAN
AWK
sed
C++
Perl
Ruby
Javascript
VBA
Haskell

readFile はファイルを読み込む。 mapM_ は動作を繰り返す。この場合は引数に指定したファイルについて繰り返す。

>cat week.txt year.txt
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Jan
Feb
Mar
Apl
May
Jun
Jul
Aug
Seb
Oct
Nov
Dec