ようこそ。睡眠不足なプログラマのチラ裏です。

Haskellのお勉強 その9

Haskellの基本的な構文

コメント
ラインコメントとブロックコメントがある。
ブロックコメントはネスト可能。

Prelude> 1 + 2 --test
3
Prelude> 1 + {-test-} 2
3
Prelude> 3 * {- test {- hogehoge -}-} 2
6

また、リテレイト形式といって、基本コメントのドキュメントの中に
コードを埋め込んで記述する方法も提供されている。
リテレイト形式で記述されたファイルの拡張しは「lhs」とするのが慣習。


レイアウト
これまではオフサイドルールといって、式のインデントをそろえることで
ブロックを表現する方法を使っていた。

main = do cs <- getContents
          putStr cs

それ以外にも、中括弧{}とセミコロン;でブロックを表現することもできる。

main = do { cs <- getContents;
  putStr cs;
  }


オフサイドルールでは、同じ高さの部分がブロックになるので、こういうこともできる。

main = do
  cs <- getContents
  putStr cs


そんでもって、可読性が悪いので誰もこんなことはしないだろうけど、
こんな風に書いちゃうこともできる。

main = do
  cs <-
    getContents
  putStr
    cs


if文
これまでも扱ってきたif文について復習。


手続き型言語においては、if文は制御構造ですが、
宣言型言語では違います。つまり、Haskellではif文が値を持ちます。
多くの関数型プログラミング言語やRubyなんかでも
if文は制御構造ではなく値を持ちますね。

Prelude> if 1 == 2 - 1 then "(^o^)" else "(T_T)"
"(^o^)"


パターンマッチ

■変数パターン

id :: a -> a
id x = x

仮引数のxが変数パターンとなる。
変数パターンはどんな値に対してもマッチし、その値に変数を束縛する。


■「_」パターン(ワイルドカード

const :: a -> b -> a
const x _ = x

引数の「_」が「_」パターン(ワイルドカード)となる。
「_」パターンは変数パターンと同じく任意の値にマッチするが、変数を束縛しない。
このパターンでは、関数の引数は必要だが、具体的な値を必要としない場合に利用します。


例えば、以下map関数の定義を見ると、空リストが引数として渡される場合、
関数fは必要ではないので、「_」を活用することができます。

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs


リテラルパターン

expandTab :: Char -> Char
expandTab '\t' = '@'
expandTab c = c

上記の例では、引数の「'\t'」がリテラルパターンです。
リテラルパターンは、値と指定したリテラルが等しいかどうかを
(==)関数で検査し、等しいときにマッチします。リテラルパターンに使えるリテラル
「数値リテラル」、「文字リテラル」、「文字列リテラル」です。


■タプルパターン

format :: (Int,String) -> String
format (n,line) = rjust 10 (show n) ++ " " ++ line

上記の例では、「(n,line)」がタプルパターンです。
タプルパターンは、その名のとおりタプルにマッチするパターンで
タプルの各要素に対して任意のパターンを記述することができる。
上記の例では、タプルの各要素に変数パターンが使われています。


■リストパターン

last []     = error "last []"
last [x]    = x
last (_:xs) = last xs

上記の例の「[x]」がリストパターンです。
リストパターンはリストにマッチするパターンで、
リストの各要素に対して任意のパターンを記述することができる。
上記の例では、要素が1つだけのリストに対してマッチし、その要素に変数xを束縛しています。
なお、[]と(x:xs)はリストパターンではなく、
この2つは次に述べるデータコンストラクタパターンです。


■データコンストラクタパターン

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs

」と[(x:xs)]はデータコンストラクタパターンです。
」は空リストを作成するデータコンストラクタです。
特定の型の値を作成するときに使われます。
また、「:」は、新しいリストを作成するデータコンストラクタです。
データコンストラクタと、それを利用したパターンについては、
第9章で再度お勉強するとのこと。


「@」パターン(アズパターン)

Prelude> import Char
Prelude> let ltrim str@(c:cs) = if isSpace c then ltrim cs else str
Prelude> ltrim "     aaa b c"
"a b c"

上記は「str@(c:cs)」で1つの値にマッチするパターンです。
このアズパターンでは、(c:cs)というデータコンストラクタパターンにマッチさせつつ、
その値全体に変数strを束縛させることができる。


ガード
パターンマッチでは値がパターンにマッチするか検査することができるが、
その方法はこれまでに出てきたパターンに限定される。
それに対してガード(guard)ではBool型の任意の式を使って値を検査することができる。

main = do
 putStrLn $ hoge 6 6
 putStrLn $ hoge 5 9
 putStrLn $ hoge 0 (-1)

hoge x y 
     | x == y = "eq"
     | x > y  = "gt"
     | x < y  = "lt"

引数パターンの後に続く「|」と「=」の間がガードとなる。
ガードは上から順に評価され、その値がTrueの場合、そのガードに対応する定義が適用される。


case式
関数の引数以外でもパターンマッチやガードを使いたいような場合は、case式が使える。

myCaps :: String -> String
myCaps str = case str of 
                  "" -> ""
                  (c:cs) -> toUpper c : cs

case式のof以降はコードブロックがあるとみなされるので、
以下のように、レイアウトではなくブレース構文を利用してもよかです。

myCaps :: String -> String
myCaps str = case str of
               { "" -> "";
                 (c:cs) | isLower c -> toUpper c : cs;
                 (c:cs) | otherwise -> c : cs }

あるいは、ガード部分を揃えて下記のように書くこともできる。

myCaps :: String -> String
myCaps str = case str of
               { "" -> "";
                 (c:cs) | isLower c -> toUpper c : cs
                        | otherwise -> c : cs }

ガードをまとめて書いているので、ブレース構文内にセミコロンがないことに注意。