ホーム > タグ > Haskell
Haskell
ふつケル第3章
- 2008-02-27 (水)
- article
型と値
Haskellには型推論という機能がある。型推論は処理系の方で型を推測し、矛盾なく全ての式が型づけできればコンパイル時にエラーにはならない。Haskell以外にも、OCaml、Scala、MLなど静的型付け関数型言語のほとんどが型推論の機能を供えている。
Haskellの型には、Int(整数値)、Char(文字)、String(文字列)、Bool(真偽値)がある。Intは最低30ビット幅の符号付き整数値を意味する。CharやStringで扱う文字(列)のエンコードにはUnicodeを採用しているが、GHCの実装は中途半端で入出力でのエンコーディング変換などが実装されていない。
また、Int型のリスト、Char型のリストと言う風にリストの型を表現し、Int型のリストの各要素はInt型になる。ソースコード上では[Int]や[Char]と書く。文字列は文字のリストなので[Char]ということになり、別名として上記したString型が用意されている。
関数の型をは引数の型と返り値の型を組合せて表現する。例えば、String -> [String]というのは第1引数の型がString(文字列)で、返り値の型が[String](文字列のリスト)となる。
第1引数の型 -> 第2引数の型 -> ・・・ -> 返り値の型
では、引数の型が決まっていない場合どうするのかと言うと、型変数と言うものを使う。型変数はアルファベット小文字であらわし、アルファベット小文字の部分は好きな型に読み替えてよい。
[a] -> Int
Int -> [a] -> [a]
上記の場合だと、1行目は任意の型のリストを引数にもち、Intを返す関数。2行目はIntを第1引数、任意の型のリストを第2引数にもち、第2引数と同じ型のリストを返す関数という意味になる。
関数の型を宣言するには、先程関数の型を表現したものに少し付け足せばいい。Haskellは型推論の機能をもつので必ずしも型の宣言が必要というわけではないが、なるべく宣言しておくのが好ましい。
関数名 :: 第1引数の型 -> 第2引数の型 -> ・・・ -> 返り値の型
高階関数
高階関数とは引数に関数をとる関数のこと。
main = do cs < - getContents
putStr $ expand cs
expand :: String -> string
expand cs = map translate cs
translate :: Char -> Char
translate c = if c == '\t' then '@' else c
このプログラムでは5行目で高階関数のmap関数が使われている。map translate csと言うのは、translate関数そのものにmapを適用しているのであって、translateの結果にmapを適用しているわけではない。
mapの型は
map :: (a -> b) -> [a] -> [b]
となっており、(a -> b)は「a型の値を引数にとりb型の値を返す関数」を意味する。今回の場合だと、translate関数の型が Char -> Char となっているので、「Char型の値を引数にとりChar型の値を返す関数」と読み替えることができる。更にいうと(a -> b) -> [a] -> [b] は (Char -> Char) -> [Char] -> [Char]となる。
main = do cs < - getContents
putStr $ expand cs
expand :: String -> String
expand cs = concat $ map expandTab cs
expandTab :: Char -> String
expandTab c = if c == '\t' then " " else [c]
この辺はちょっとややこしくなってくるが、ちゃんと順に考えていけば問題ない。
パターンマッチ(1)
tabStop = 8
main = do cs < - getContents
putStr $ expand cs
expand :: String -> String
expand cs = concatMap expandTab cs
expandTab :: Char -> String
expandTab '\t' = replicate tabStop ' '
expandTab c = [c]
個人的にtabStopがグローバル変数みたいでなんか気持ち悪い。定数と考えればいいのかな。まぁ今はとりあえず置いておこう。
今回のプログラムの最後2行でパターンマッチを使用している。下から2行目が引数が「\t」の時に使われる定義。最後の行は仮引数が使われているので、「どんな値」にもマッチする。パターンマッチは上に書いたものが優先され、マッチするパターンが無い場合には実行時にエラーが発生する。
関数名 第1引数のパターン 第2引数のパターン ・・・・ = 定義1
関数名 第1引数のパターン 第2引数のパターン ・・・・ = 定義2
関数名 第1引数のパターン 第2引数のパターン ・・・・ = 定義3
パターンマッチ(2)
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
2行目のパターンマッチは空リストに一致する。つまりmapの第2引数が[]だった場合、必ず[]を返すと言うことになる。
3行目の(x:xs)もリストに対するパターンで、空リスト以外のリストにマッチする。リストの最初の要素がxに束縛され、残りの要素がxsに束縛される。まぁSchemeのcarやcdrみたいなものかな。
3行目での定義ではまたさらにmapを適用している。つまり、再帰を用いて処理している。Haskellにはループを扱う構文は存在しない為である。
map length ["abc", "de", "f"]
length "abc" : map length ["de", "f"]
length "de" : map length ["f"]
length "f" : map length []
(1 : [])
(2 : [1])
(3:[2,1])
[3, 2, 1]
例えば「map length [“abc”, “de”, “f”」だと多分こんな感じ。「:」演算子はリストを生成する演算子で(y:ys)ならリストysの先頭に要素yを追加する。
練習問題
main = do cs < - getContents
putStr $ swapa cs
swapa :: String -> String
swapa cs = map replaceA cs
replaceA :: Char -> Char
replaceA 'a' = 'A'
replaceA 'A' = 'a'
replaceA c = c
とりあえず動く。
追記(08/02/27)
main = do cs < - getContents
putStr $ map swapa cs
swapa :: Char -> Char
swapa 'a' = 'A'
swapa 'A' = 'a'
swapa c = c
書き直した。この方が短い。
- Comments: 0
- Trackbacks: 0
ふつケル第2章
- 2008-02-25 (月)
- article
ハロー、ワールド
main = putStrLn "Hello, World!"
「main = 」は変数mainの定義. 「putStrLn “Hello, World!”」が変数mainの値となる。putStrLnは標準出力に文字列と開業を出力する時に使う関数で、正確に言うと文字列を1つ受けとり、その文字列と改行を出力するアクションを返す関数.
「putStrLn “Hello, World!”」の式を、Haskellでは“Hello,World!”にputStrLn関数を適用すると言うらしいけど、今一ピンとこないな. 普段Rubyやってるからかな. putStrLn関数を”Hello,World!”にあてはめて用いると言いなおせばまだわかるかな. ・・・あんまり変わらないか.
cat
main = do cs < - getContents
putStr cs
1行目のcs < - getContentsとputStr csの2つの式が揃っているのには意味があるらしい。インデントに意味があるなんてPythonみたいなやつだ。複数の式のインデントを揃えることで、do式で1つのブロックに束ねることができる。この規則をレイアウトやオフサイドルールと言う。複数のアクションをdo式でまとめると上から下へ実行されることが保証される。
アクションの結果を得るときに「< -」を使う。このサンプルの場合だと、getContentsアクションで入力した文字列が変数csに結びつき、それを「変数を値(アクションの結果)に束縛する(bind)」と言う。値が変数を束縛するのか。値は寂しがりやさんだな。
このサンプルには遅延評価も関連してるが、ここではあまり詳しく触れてないのでスルー。
countline
main = do cs < - getContents
print $ length $ lines cs
Haskellにおいてリストはかなり重要。Haskellでは文字列もリストとなっている。リストには一種類の値しか入れられない。このあたりはRuby、JavaScriptあたりをやっていると忘れがちなので注意。
['a', 'b', 'c'] => "abc"
[1, 2, 'a'] => NG
$演算子は+や-と同じような二項演算子。ここでは式を区切るために使われている。$を()に置き換えると以下のようになる。
main = do cs < - getContents
print (length (lines cs))
head
main = do cs < - getContents
putStr $ firstNLines 10 cs
firstNLines n cs = unlines $ take n $ lines cs
4行目のfirstNLinesは関数の定義。基本的にmainの時と同じだけど、今回は仮引数が登場している。nとcsがそれぞれ第1仮引数、第2引数。
関数名 仮引数1 仮引数2・・・ = 関数本体
tail
main = do cs < - getContents
putStr $ lastNLines 10 cs
lastNLines n cs = unlines $ takeLast n $ lines cs
takeLast n ss = reverse $ take n $ reverse ss
今迄に登場したものだけを使っているので、特別なことはなし。
練習問題
countbyte.hs
main = do cs < - getContents
print $ length cs
countword.hs
main = do cs < - getContents
print $ length $ words cs
とりあえずチャチャっと書いてみた。countword.hsは単語数だから問題ないと思うけど、countbyte.hsは日本語の全角文字とかも1byte扱いすると思う。と思ったらちゃんと計算してくれた。でも、ghciで"あ"のlengthをとると1が返ってくる。よくわからん。
Prelude> length "あ"
1
- Comments: 0
- Trackbacks: 0
Home > Tags > Haskell
- Feeds
- Meta
- Others
