局所的な定義と where 節

ある関数の内部で別の関数が評価されるとき,もしその内部の関数がプログラムのほかのどの部分でも評価されないなら--言い換えればその場でしか使われないなら,その関数を局所的な定義にすることができる。局所的に定義された関数はプログラムのほかの部分からは見えなくなる。
局所的な定義はインデントを下げて where と書いたあとに続けて通常の関数定義と同じように書く。
where 以降を where 節という。

というわけで Capitalize をこうしてみた(関数名は先頭に大文字を使えないから capitalize)。

import Data.Char

capitalize []     = []
capitalize (c:cs) = (toUpper c):(lower cs)
    where lower []     = []
          lower (c:cs) = (toLower c):(lower cs)

先頭の文字だけ大文字にして,2文字目以降は局所的に定義された lower を適用している。where 以降が lower の定義。

実行結果。

*Main> capitalize "haskell"
"Haskell"

追記:
map を使えば lower なんて関数を定義しなくてもすむ。

capitalize []     = []
capitalize (c:cs) = (toUpper c):(map toLower cs)
*Main> capitalize "haskell"
"Haskell"

capitalizE

Capitalize ならぬ capitalizE。字面どおり最後の文字だけを大文字に変える。
……いや,名前はともかく(っていうか,こんな動作に名前がついてるとは思えないが)。
toUpper や toLower を使うには Data.Char モジュールを import する。

import Data.Char

capitalizE []     = []
capitalizE [c]    = (toUpper c):[]
capitalizE (c:cs) = (toLower c):(capitalizE cs)

実行結果。

*Main> capitalizE "Haskell"
"haskelL"

OK。

で,Capitalize のほう。こうやったら全部大文字になった。

capitalize []     = []
capitalize (c:cs) = (toUpper c):(capitalize cs)
*Main> capitalize "haskell"
"HASKELL"

”リストの先頭”は常にあるんだから当然だよな。

join

要素と要素の間ごとに新しい要素を挟み込む。

join s []     = []
join s (c:cs) = c:s:(join s cs)

実行結果。

*Main> join '-' "abc"
"a-b-c-"

あれ?

そうか,一番最後の要素も c:cs にマッチングしてるんだな。てことは”最後の要素”を表すパターンがあればいいのか。これでどうだ。

join s []     = []
join s (c:[]) = c:[]
join s (c:cs) = c:s:(join s cs)

実行。

*Main> join '-' "abc"
"a-b-c"

OK。

関数の定義とパターンマッチング

「入門Haskell」から文字列の文字数を数える関数。

bytesCount []     = 0
bytesCount (c:cs) = 1 + bytesCount cs

関数名 bytesCount につづく引数の部分がパターンを表していて,受け取った引数のパターンによって関数本体の内容が変わる。
1行目の [] は空のリスト,c:cs は先頭の要素とそれ以降の要素に分けられることを表している。だからこの関数を評価すると,引数が空リストなら 0,空でないなら(先頭とそれ以降に分けられるから)先頭の文字数 1 に以降の文字数を足したもの,となる。
パターンがいくつもある場合には定義をいくつも書けばいい。パターンは上から順にチェックされる。

リストの操作は基本的に先頭から順に,再帰的にすればいいようだ。

ちょっと練習。すべての要素を足し合わせる。

mySum []     = 0
mySum (c:cs) = c + mySum cs

実行結果。

*Main> mySum [1,2,3,4,5,6,7,8,9,10]
55
*Main> mySum []
0