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 }
ガードをまとめて書いているので、ブレース構文内にセミコロンがないことに注意。