人気ブログランキング | 話題のタグを見る

RWH の読み方(40) 第4章

Real World Haskell 第4章 Functional Programming

Anonymous (lambda) Functions

このセクションでは、Haskell の無名関数の書き方について述べてある。例えば次のように関数に名前をつけなくても、無名関数を定義して使うことができる。

Prelude> (\x -> x * x) 2
4

\ は英米のASCIIではバックスラッシュになるのでギリシャ語のλに似ている。このため \ を「ラムダ」と読んで、無名関数の(独立)変数を指示するのに使われている。

関数のラムダ記法(ラムダ算法)は1930年にチャーチとクリーネによって考案された形式的に関数を表記するための記法だ。もともとは記号論理学的に一階述語論理学の決定不可能性を証明するために使われた。

ラムダ算法は関数型言語と密接な関係があり、Lisp はラムダ算法をプログラミング言語として実装したものだ。

ここでは、そのような理論的な性質は関係なく、単に無名関数を記述する方法だと理解しておけばいい。

ラムダ記法の無名関数は、map 関数に利用することが多い。

Prelude> map (\x -> x * x) [1,2,3]
[1,4,9]

また IO モナドの cs <- getLine のような <- 記法を理解するときに必要になる。

しかし、実際のプログラミングでは可読性が下がるなどの理由で避けたほうがよいとされる。上の map のプログラムもラムダ記法の部分はセクション Section で代替可能だ。

Prelude> map (^2) [1,2,3]
[1,4,9]

Partial Function Application and Crrying

Haskell の type signature は a -> b -> c のようにタイプ変数を -> でつなげてあるが、これは2引き数以上の Haskell の関数が、1引き数の関数とも、複数引き数の関数ともみなすことができるということを示している。

次の2引き数の関数 twoArgs は次のように引き数 x に定数を代入して1引き数の関数 oneArg とみなすこともできる。

Prelude> let twoArgs x y = x + y
Prelude> twoArgs 1 2
3
Prelude> let oneArg y = twoArgs 2 y
Prelude> oneArg 2
4

多変数関数を1変数関数に使いまわしたりできるので便利だ。もっと複雑な使い方もできるがそれは RWH の本文を読むと書いてある。

多変数関数に部分的に値を適用して新しい関数を作ることを currying と呼ぶ。

Partial Function にしても、crrying にしても実際のプログラムを書くときは頻繁に出てくるのでその威力を感じることが多いが、文章で説明するのは難しい。RWH の例を読んでもあまり有難みは感じないかもしれないが、自分で Haskell の小さなプログラムを書き始めると自然に利用し始めるだろう。

Sections

Section は中置演算子の partially applied function だ。例えば + は、x + y のように2つの引き数をとりその和を計算するが、これを (1+) と記述すると左の引き数が1で右の引き数が y の関数になる。したがって引き数 y の値を与えるとその和が計算される。

Prelude> (1+) 2
3

Section は map 関数と組み合わせて使われることが多い。例えば上にも例示したが次のようなプログラムは、リストの引き数の各要素を二乗した新しいリストを返す。

Prelude> map (^2) [1,2,3,4,5]
[1,4,9,16,25]

詳しい説明は RWH の本文に書かれている。

As-patterns

再帰関数の the recursive part を記述するときに次のように (x:xs) というパターンを使うが、関数の body の部分で (x:xs) そのものを使いたい時がある。そのときは xs'@(x:xs) のように @ マークを使ったパターンを使うと関数の body の部分で xs' を (x:xs) として使うことができる。

RWH の例にある suffixes 関数を ghci 上で検証してみた。

Prelude> let suffixes xs@(x:xs') = xs : suffixes xs'; suffixes _ = []
Prelude> suffixes [1,2,3,4]
[[1,2,3,4],[2,3,4],[3,4],[4]]

As-pattern の詳しい説明は RWH の本文を参照してほしい。

Code Reuse Throught Composition

このサブセクションでは、関数の使い回しのパターンを説明してある。Haskell は関数を合成することで新しい関数を作り出すことができるので、関数の再利用が簡単にできる。そのため、車輪を二度発明しないという原則を実行しやすい環境がある。

RWH では As-pattens のセクションで記述した suffixes という関数を標準関数の再利用で記述して見せている。

suffixes を文字列 "foo" に関数適用すると次のような結果になる。

Prelude> suffixes "foo"
["foo","oo","o"]

ところで、Data.List モジュールの関数 tails を "foo" に関数適用すると次のようになる。

Prelude> :m +Data.List
Prelude Data.List> tails "foo"
["foo","oo","o",""]

値のリストの最後の空文字列 "" が余計なので、init 関数で取り除いてやる。

Prelude Data.List> init (tails "foo")
["foo","oo","o"]

これで suffixes 関数と同じ動作を既存の関数を組み合わせてできることが分かったので。関数再利用のプログラム suffixes2 を定義する。

Prelude Data.List> let suffixes2 xs = init (tails xs)
Prelude Data.List> suffixes2 "foo"
["foo","oo","o"]

このように既存の関数を合成して新しい関数を定義するのか簡単にできるところが Haskell の魅力のひとつだ。

ところで、init (tails xs) という式は init 関数 と tails 関数の合成を意味しているので合成関数を作る演算子 . を利用すると次のように定義できる。

Prelude Data.List> let suffixes3 xs = (init . tails) xs
Prelude Data.List> suffixes3 "foo"
["foo","oo","o"]

上の定義では関数の合成だけに注目して引き数を定義式から省略することもできる。

Prelude Data.List> let suffixes4 = init . tails
Prelude Data.List> suffixes4 "foo"
["foo","oo","o"]

RWH では関数の再利用を使って、C言語のマクロ名のみを取り出すプログラムを説明しているが、プログラムを再掲するだけに留める。

-- file: ch04/dlts.hs
import Data.List (isPrefixOf)

dlts :: String -> [String]

dlts = foldr step [] . lines
  where
    step l ds
      | "#define DLT_" `isPrefixOf` l = secondWord l : ds
      | otherwise = ds
    secondWord = head . tail . words

実行例

*Main Data.List> dlts "#define DLT_CHAOS 5"
["DLT_CHAOS"]

Use Your Head Wisely

このサブセクションではランタイムエラーを防ぐためにパターンの記述の際に起こりえるパターンの全ての可能性を考慮するように勧めている。

Tips for Writing Readable Code

このセクションでは可読性の高いコードを書くための要件について述べている。

その要件とは、

1. 無名関数や末尾再帰を使わないこと。
2. 極力ライブラリー関数を使って、ベタな再帰関数を書かないこと。
3. ライブラリー関数でできることを folds を使って書かないこと。
4. let や where を活用して局所関数もできるだけ名付きの関数を使うこと。

などのようだ。詳しくは RWH の本文に書いてある。

Space Leaks and Strict Evaluation

Haskell は遅延評価が標準だが、実用的なプログラムの場合は、正格評価を利用しなければならないことが多い。正格評価を強制する関数は seq だが、このセクションでは seq を利用した正格評価について述べてある。しかし、内容も難しいし、さしあたって必要ではないので省略する。

この記事でやっと第4章 Functional Programming が終了した。ここまでの知識で基本的な Haskell プログラムなら十分書けるようになっていると思う。

第5章以下はだんだんに実用的なプログラムを Haskell で書くにはどうするかという RWH 本来の目的が始まってくるが、ちょっと記事を書くのにつかれてきたので本当にしばらく RWH 関係の記事を終了したい。
by tnomura9 | 2012-04-29 15:26 | Haskell | Comments(0)
<< YouTube の踊ってみた画... RWH の読み方(39) 第4章 >>