リストを操作する関数(その3)

foldl と foldr はちょっとわかりにくい。なのでこんなのを書いてみた。
括弧内の数字は引数を表している。0 が初期値で 1, 2, 3… がリストの各要素。内側の括弧から順に評価される。
foldl はこう。

*Main> foldl (\x y -> "(" ++ x ++ "," ++ y ++ ")" ) "0" ["1","2","3","4","5"]
"(((((0,1),2),3),4),5)"

で foldr はこう。

*Main> foldr (\x y -> "(" ++ x ++ "," ++ y ++ ")" ) "0" ["1","2","3","4","5"]
"(1,(2,(3,(4,(5,0)))))"

何となくわかりやすい……?

ガード節

パターンマッチングの後,引数の具体的な値を条件に分岐したい場合には次のように書く。
| と = の間の分岐条件の部分をガード節という。
条件は上から順にチェックされる,でいいのかな。otherwise は「その他の場合」。

swapCase は文字列の中の大文字と小文字を入れ替える。アルファベットでないならそのまま。

import Data.Char

swapCase []     = []
swapCase (c:cs)
    | isUpper c = (toLower c):(swapCase cs)
    | isLower c = (toUpper c):(swapCase cs)
    | otherwise = c:(swapCase cs)
*Main> swapCase "Haskell"
"hASKELL"
*Main> swapCase "03^-c,aa"
"03^-C,AA"

局所的な定義と 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

href

via. http://d.hatena.ne.jp/nskj77/20060401

コマンドラインから使えるリファレンス。Refe の Haskell 版。バージョンは 0.3.1。

http://i.loveruby.net/w/href.html

インストールは README.jp に書いてあるとおりにすれば簡単……とはいかない。Windows だから。
仕方がないので Makefile をながめて次のように書き換えた。換えたところだけ示す。

bindir = c:\usr\local\bin
EXEEXT = .exe
# mkdir -p $(bindir)

1行目はコマンドのインストール先,c:\usr\local\bin にインストールすることにした。
2行目は実行ファイルの拡張子。3行目は今回はすでにフォルダがあるので全体をコメントアウトした。もしないなら -p オプションだけ削除する。
これで良し。make はないので nmake を使う。

>nmake
Microsoft (R) Program Maintenance Utility   Version 1.50
Copyright (c) Microsoft Corp 1988-94. All rights reserved.
ghc -cpp --make -package network href.hs -o href.exe
Chasing modules from: href.hs
(以下略)
>nmake install
Microsoft (R) Program Maintenance Utility   Version 1.50
Copyright (c) Microsoft Corp 1988-94. All rights reserved.
ghc -cpp --make -package network href.hs -o href.exe
Chasing modules from: href.hs
(以下略)

次,データベースの準備。
c:\usr\share\href にインストールするので c:\usr\share を先に作っておく。href はなくてもいい。

>set HREF_DATADIR=c:\usr\share\href
>mkhref ref/*.*

これでOK。あとはコントロールパネルで環境変数 HREF_DATADIR を設定しておけば普通に使える。

>href filter
Data.List.filter
filter :: (a -> Bool) -> [a] -> [a]    PRELUDE
filter f xs
・・ケ・ネ xs 、ホ、ヲ、チ f 、ャ True 、ヒ、ハ、・ラチヌ、タ、ア、・
スク、皃ソ・・ケ・ネ、ヨ、ケ。」

って,文字化けしてる!……EUCかぁ。

追記:
ref 以下のファイルを ShiftJIS に変換してからインストールしたら直った。
変換に使った Ruby のスクリプト。

require 'find'
require 'fileutils'
require 'nkf'

src = ARGV.shift
dest = ARGV.shift

Find.find(src) do |f|
  case File.ftype(f)
  when "directory"
    FileUtils.mkdir_p(f.sub(src, dest))
  when "file"
    File.open(f, "r") do |infile|
      File.open(f.sub(src,dest), "w") do |outfile|
        outfile.write NKF.nkf("-E -s", infile.read)
      end
    end
  end
end

さらに追記:
むぅ,こうすればコマンド2つですんだ。

>mkdir ref.sjis
>for %X in (ref/*) do nkf -E -s ref/%X > ref.sjis/%X

リストを操作する関数(その2)

zip は2つのリストからそれぞれの要素を取り出して,タプル(組)にしたリストを返す。

Prelude> zip [1,2,3] "abc"
[(1,'a'),(2,'b'),(3,'c')]

タプルには違う型を含めることができる。
zipWith はタプルを作る代わりに関数を適用する。

Prelude> zipWith (+) [1,2,3] [10,20,30]
[11,22,33]

上の例では + を zipWith の第1引数と渡しているけど,()で囲んでやらないとエラーになる。
うまく zipWith の引数として認識できないってことかな。

Prelude> zipWith + [1,2,3] [10,20,30]
<interactive>:1:10:
Couldn't match `[a]' against `t -> t1'
Expected type: [a]
Inferred type: t -> t1
Probable cause: `[1, 2, 3]' is applied to too many arguments in the call
([1, 2, 3] [10, 20, 30])
In the second argument of `(+)', namely `[1, 2, 3] [10, 20, 30]'

ところでタプルって何に使うんだろ。

追記: zipWith の引数について
こうすると同じメッセージがでる。つまり二つのリストが zipWith ではなく + の引数だと解釈されてしまうってことか。

Prelude> zipWith (+ [1,2,3] [10,20,30])
<interactive>:1:11:
Couldn't match `[a]' against `t -> t1'
Expected type: [a]
Inferred type: t -> t1
Probable cause: `[1, 2, 3]' is applied to too many arguments in the call
([1, 2, 3] [10, 20, 30])
In the second argument of `(+)', namely `[1, 2, 3] [10, 20, 30]'

リストと値の型

ひとつのリスト中に違う型の値を含めることはできない。

Prelude> [1,2,'a']
<interactive>:1:1:
    No instance for (Num Char)
      arising from the literal `1' at <interactive>:1:1
    Probable fix: add an instance declaration for (Num Char)
    In the list element: 1
    In the definition of `it': it = [1, 2, 'a']

当然連結もダメ。

Prelude> [1,2,3] ++ ['a','b','c']
<interactive>:1:1:
    No instance for (Num Char)
      arising from the literal `1' at <interactive>:1:1
    Probable fix: add an instance declaration for (Num Char)
    In the list element: 1
    In the first argument of `(++)', namely `[1, 2, 3]'
    In the definition of `it': it = [1, 2, 3] ++ ['a', 'b', 'c']

リストを操作する関数

head はリストの先頭の要素,tail は先頭以外の要素を返す。

Prelude> head [1,2,3,4,5]
1
Prelude> tail [1,2,3,4,5]
[2,3,4,5]

最後の要素を得るには last が使える。

Prelude> last "abc"
'c'

文字列にも使える。

Prelude> head "abc"
'a'

演算子も関数。 !! でインデックスに対応する要素を返し,++ でリストを連結。

Prelude> "abcde" !! 3
'd'
Prelude> "abc" ++ "ef"
"abcef"