2015-12-21 『ふつうのHaskellプログラミング』 もくもく読書会(第3章) の振り返り
ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
- 作者: 青木峰郎,山下伸夫
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/06/01
- メディア: 単行本
- 購入: 25人 クリック: 314回
- この商品を含むブログ (320件) を見る
第3章 Haskellの基礎(2) 型と高階関数
型
関数の型
- 引数の型と戻り値の型の組み合わせで関数の型を表現する
- 引数と引数の間および引数と戻り値の間に
->
を入れる - 関数名と関数の型の間に
::
を入れる - 関数の型は宣言しなくても型推論によって使えることもあるが、型宣言をすることで意図したとおりの型かチェックできる
関数名::引数1の型 -> 引数2の型 -> … -> 戻り値の型
例:lines関数の場合
lines :: String -> [String]
これは引数にStringを受け取り戻り値にStringのリストを返す関数の型をあらわす。
型変数
- リストの要素のように型を特定しないときに使う変数
- 型変数を含む型のことを多相型(polymorphic type)と言う
- 型変数はアルファベットの小文字から始まる識別子なら何でも使える
- 慣例的にa,b,c…など1文字が型変数に使われる
- 1つの式に複数の多相型を持つ場合、それが同じ型なら同じ型変数、違う場合なら違う型変数を使う
例:同じ型変数の場合(take関数)
take :: Int -> [a] -> [a]
例:違う型変数の場合(map関数)
map :: (a -> b) -> [a] -> [b]
高階関数
- 関数を引数に取る関数
値としての関数
- 関数は値として使われる
- 関数名は変数
→ squareを「nを2乗する関数」の変数と見ることもできる
例:
square n = n * n
- 引数や戻り値で「a型の値からb型の値を返す関数」をあらわす場合、
(a -> b)
と記述する
map関数
- リストの各要素に対し引数の関数を適用する関数
- Haskellではよく使われる関数の一つ
例:[1,2,3]がどのように変わるのか段階的に見ていく
map square [1,2,3]
↓
[(square 1), (square 2), (square 3)]
↓
[1,4,9]
expand.hs
- 入力内容のタブ文字を空白文字に展開するコマンド
- map関数を使う
バージョン0
まずはタブ文字を「@」に変換するようにするコマンドを作成する
expand::String -> String expand cs = map translate cs translate :: Char -> Char translate c = if c =='\t' then '@' else c
※ Windowsの場合バックスラッシュは円マークになる
- expand関数は引数の
cs
をtranslate関数に適用する cs
は入力されたStringでStringは[Char]である- translate関数は引数の文字cをif式で判定し対象の値を返す
if式
if 条件式 then 式1 else 式2
- 式1と式2はこの例では値
- if式全体が値となる
- Haskellではifは文ではなく式なのでelseを省略できない
バージョン1
- この段階ではタブ文字を空白8文字に置き換えるようにする
- expandTab関数で文字から文字列に変換しているのでconncat関数を使ってネストした文字列を外している
…略 expand cs = concat $ map expandTab cs expandTab :: Char -> String expandTab c = if c =='\t' then " " else [c]
AL\tAlabama…
と言う文字列がmap expandTab cs
で下記のようなリストになる。また\t
は空白8文字の文字列だが、これは空白文字が8つのリストと同じ。
[['A'], ['L'], [' ', ' ', …],…, ['m'], ['a']]
↓ これをconcat関数を使って中のリストの囲いを外す
['A', 'L', ' ', ' ', …, …, 'm', 'a']
↓
"AL Alabama"
concat関数
concat::[[a]] -> [a]
- リスト内にあるリストの囲いを外す関数
- 外す囲いは1つのみ
パターンマッチ
- 関数に引数のパターンを指定して処理を定義する方法
- パターンマッチに該当しないものがある場合はエラー
バージョン2の例より:
expandTab :: Char -> String expandTab '\t' = 処理1 expandTab c = 処理2
この場合、タブ文字だと処理1をそれ以外だと処理2を行う
バージョン2
変更箇所は下記
concat関数の個所
concat $ map expandTab cs
↓
concatMap expandTab cs
expandTab関数の個所
expandTab c = if c =='\t' then " " else [c]
↓
expandTab '\t' = replicate tabStop ' ' expandTab c = [c]
まずconcat $ map expandTab cs
と2つの関数を使っていた箇所をconcatMap expandTab cs
と標準で用意されているconcat関数とmap関数を1つにまとめた関数を使うように変更。
expendTab関数についてはif式で条件分岐していたものをパターンマッチに変更
また、" "
と直接空白文字を埋めていた箇所をreplicate tabStop ' '
と変更
※ tabStop = 8
replicate関数
replicate :: Int -> a -> [a]
replicate関数は第1引数にIntを取り第2引数の値を第1引個のリストを作成する関数
replicate tabStop ' '
は空白文字8個のリスト、すなわち8個の空白の文字列になる
リストのmap関数の定義について
map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs
パターンマッチ
map関数では第2引数に対してパターンマッチを行っている
[]
は空のリストの場合は空のリストを返す。(x:xs)
は要素を持つリストのパターンでx
がhead
、xs
がtail
をあらわす- head = リストの最初の要素
- tail = リストの最初の要素を除いた全てのリスト(
head
のみの要素しか持たないリストはtail
は空リストになる)
「:」演算子
要素 : リスト
で最初の要素に指定した要素を持ち残りが指定したリストのリストを作成する演算子
Haskellではリストを
head : tail
として定義することができる
f x : map f xs
はhead
に関数fを適用したx
の値、tail
にmap関数を適用して第2引数に対象のリストのtail
部分を渡して、残りの要素も同様に関数fが適用されるようにしている
再帰
練習問題
小文字を大文字に変更する練習問題かと思ったが、答えを見たら'a'から'A'と'A'から'a'だけ変換する問題だった。 この章の目的だと、パターンマッチとmap関数を使って問題を解くべきなのだろう。
とりあえず、問題の意図をくんでなかったけど、小文字から大文字に変換するパターンで答えた例:
main = do cs <- getContents putStr $ map swapa cs swapa :: Char -> Char swapa 'a' = 'A' swapa 'b' = 'B' swapa 'c' = 'C' swapa 'd' = 'D' swapa 'e' = 'E' swapa 'f' = 'F' swapa 'g' = 'G' swapa 'h' = 'H' swapa 'i' = 'I' swapa 'j' = 'J' swapa 'k' = 'K' swapa 'l' = 'L' swapa 'm' = 'M' swapa 'n' = 'N' swapa 'o' = 'O' swapa 'p' = 'P' swapa 'q' = 'Q' swapa 'r' = 'R' swapa 's' = 'S' swapa 't' = 'T' swapa 'u' = 'U' swapa 'v' = 'V' swapa 'w' = 'W' swapa 'x' = 'X' swapa 'y' = 'Y' swapa 'z' = 'Z' swapa c = c
単純にパターンマッチで小文字の全アルファベットに対応させただけ。
実際の本の解答も似たようなものだった。