[増補改訂] 関数プログラミング実践入門
「関数プログラミング実践入門」というHaskellの本を買いました。
この本は、2016年に改訂版が出ているので、間違って古いほうを買わないように気を付けてください。
[増補改訂]関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)
- 作者: 大川徳之
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
- 目次
- 第0章 [入門]関数プログラミング ――「関数」の世界
- 第1章 [比較で見えてくる]関数プログラミング ――C/C++,Java,JavaScript,Ruby,Python,そしてHaskell
- 第2章 型と値 ――「型」は,すべての基本である
- 第3章 関数 ――関数適用,関数合成,関数定義,再帰関数,高階関数
- 第4章 評価戦略 ――遅延評価と積極評価
- 第5章 モナド ――文脈を持つ計算を扱うための仕掛け
- 第6章 オススメの開発/設計テクニック ――「関数型/Haskellっぽい」プログラムの設計/実装,考え方
- 第7章 Haskellによるプロダクト開発への道 ――パッケージとの付き合い方
- 第8章 各言語に見られる関数プログラミングの影響 ――Ruby,Python,Java,JavaScript,Go,Swift,Rust,C#,C++
- Appendix
- 出版社
- 著者
- 書評
目次
本書について
本書の構成
増補改訂における,おもな変更点
本書で必要となる前提知識
謝辞
第0章 [入門]関数プログラミング ――「関数」の世界
0.1 関数プログラミング,その前に ――実用のプログラムで活かせる強みを知る
関数プログラミングから得られる改善
0.2 関数とは何か? ――命令型言語における関数との違い
関数プログラミングにおける関数
副作用
0.3 関数プログミングとは何か? ――「プログラムとは関数である」という見方
プログラミングのパラダイム
命令型プログラミングのパラダイム
オブジェクト指向プログラミングのパラダイム
関数プログラミングのパラダイム ――プログラムとは「関数」である
関数の持つモジュラリティ ――「プログラムを構成する部品」としての独立性
0.4 関数型言語とは? ――関数が第一級の対象である? 代入がない?
関数型言語であるための条件
関数のリテラルがある
関数を実行時に生成できる
関数を変数に入れて扱える
関数を手続きや関数に引数として与えることができる
関数を手続きや関数の結果として返すことができる
関数型言語と命令型言語
代入がないことから得られるもの
Column いろいろな関数型言語
0.5 関数型言語の特徴的な機能 ――型の有無,静的/動的,強弱
型付きと型なし
静的型付けと動的型付け
純粋
型検査
強い型付けと弱い型付け
型推論
Column 弱い型付けは何のため?
依存型
評価戦略
おもな関数型言語と命令型言語の機能一覧
0.6 なぜ今関数型言語なのか? ――抽象化,最適化,並行/並列化
関数型言語の抽象化 ――数学的な抽象化とは?
関数型言語の最適化
関数型言語と並行/並列プログラミング
並行/並列という概念とプログラミングの難しさ
目的から考える並行/並列プログラミング
並行プログラミングの難しさ ――競合状態,デッドロック
並列プログラミングの一助 ――参照透過性の保証
Column 関数型言語と定理証明
0.7 関数型言語と関数プログラミングの関係 ――強力な成果を引き出すために
関数プログラミングの導入 ――命令型でも活かせる技法
関数型言語による関数プログラミングの導入
0.8 関数型言語の歴史 ――過去を知り,今後を探る
関数型言語のこれまで
関数型言語のこれから
進化の方向
普及可能性
0.9 関数型言語を採用するメリット ――宣言的であること,制約の充足のチェック,型と型検査,型推論
宣言的であることのメリット
制約の充足をチェックしてくれるメリット
型と型検査があることのメリット
型推論のメリット
0.10 本書で取り上げる関数型言語 ――Haskellの特徴,実装,環境構築
Haskellが持つ特徴的な機能
Column 世界で一番美しい? クイックソート?
Haskellの実装
Haskell環境の構築
対話的インタープリタGHCiの基本的な使い方
コンパイラGHCの基本的な使い方
0.11 まとめ
Column 現在関数型言語が採用されている分野/プロダクト
第1章 [比較で見えてくる]関数プログラミング ――C/C++,Java,JavaScript,Ruby,Python,そしてHaskell
1.1 部品を組み合わせる ――合う部品のみ合わせられる力
同じものから同じものへの変換を組み合わせる
2次元の座標変換
C言語の場合 ――合わない部品
JavaScriptの場合 ――合うかもしれない部品を作り/合わせる力
組み合わせやすさは部品化の大前提
さらなる部品化
Haskellの場合 ――合う部品を作り/合う部品のみ合わせる力
1.2 文脈をプログラミングする ――NULL considered harmful
NULLが示すもの
NULLの危険性
値がないことを扱う方法
C++(boost::optional)の場合 ――強力過ぎる例外処理のボイラープレート
Javaの場合 ――ネストしていくメソッドチェイン
Haskellの場合 ――行間に処理を発生させることのできる力
1.3 正しい並列計算パターン ――計算パターンの変化と影響
C(OpenMP)の場合 ――アノテーションによる並列化
要件追加に対応するための不用意な変更
失敗の原因と正しい変更
Haskellの場合 ――危険な並列化の排除
要件追加に対応する変更
純粋であることによって守られたもの
Column それでも並列化は難しい
1.4 構造化データの取り扱い ――Visitorパターン
Java(Visitorパターン)の場合 ――肥大化と引き換えの柔軟性
Haskellの場合 ――型の定義/利用のしさすさ
コード量の差が生じる要因
型を簡単に定義できる
パターンマッチがある
1.5 型に性質を持たせる ――文字列のエスケープ
HTMLの文字列エスケープ
Rubyの場合 ――性質の改変は利用者の権利
「エスケープ済みである」という性質をクラスで保護/保証する
保証を破れる言語機能の存在
Haskellの場合 ――性質の保証は提供者の義務
「エスケープ済みである」という性質を型で保護/保証する
保証した性質を破らせない
「型システムが強力である」ことが意味するもの
――その場所場所で,適切な型付けの度合いを選択する余地がある
1.6 文書をルール通りに生成する ――安全なDSL
構造を持つ文書とルール
プログラムから文書を生成する方法
Pythonの場合 ――Jinja2で生成してみる
Haskellの場合 ――言語内DSL
文脈にまで性質を持たせる
1.7 まとめ
第2章 型と値 ――「型」は,すべての基本である
2.1 Prelude ――基本のモジュール
基本のPreludeモジュール ――モジュールのimportの基本
2.2 値 ――操作の対象
値の基本
リテラル ――値の表現,およびその方法
数値リテラル
文字リテラル
文字列リテラル
ラムダ式 ――関数のリテラル
値コンストラクタ ――Haskellの真偽値True/Falseは値コンストラクタ
2.3 変数 ――値の抽象化
変数
定数
束縛
2.4 型 ――値の性質
型の基本
型の確認と型注釈
関数の型
カリー化
意図的に避けた型の確認
型検査
多相型と型変数
リスト
タプル
Either
Maybe
型推論
2.5 型を定義する ――扱う性質の決定
既存の型に別名を付ける ――type宣言
既存の型をベースにした新しい型を作る ――newtype宣言
完全に新しい型を作る ――代数データ型
代数データ型の定義の基本 ――HTTPステータス
レコードを使う ――色空間RGBA
多相型に定義し直してみる ――2次元の座標空間
再帰型の定義 ――自然数
多相型と再帰型 ――2分木
代数データ型と直積/直和
2.6 型クラス ――型に共通した性質
型クラスとは何か?
型クラスを調べる
いろいろな型クラス
Show
Read
Num
Fractional
Floating
Integral
Eq
Ord
Enum
Bounded
2.7 よくある誤解 ――実行時型情報を利用したい
型を見て分岐したい
実行時型情報と型検査の相性
2.8 まとめ
Column コンストラクタ名に惑わされず,データの構造を捉える
第3章 関数 ――関数適用,関数合成,関数定義,再帰関数,高階関数
3.1 関数を作る ――既存の関数から作る,直接新たな関数の定義する
関数を作る方法
3.2 関数適用 ――既存関数の引数に,値を与える
関数適用のスペース
関数適用の結合優先度
関数の結果としての関数との関数適用
関数の2項演算子化
2項演算子の関数化
セクション
部分適用
3.3 関数合成 ――既存の関数を繋げる
関数合成と,合成関数
3.4 Haskellのソースファイル ――ソースファイルに関数を定義し,GHCi上でそれを読み込む
サンプルファイルの準備とGHCiへの読み込み
ソースファイルへの追加/編集,再読み込み
3.5 関数定義 ――パターンマッチとガード
一般的な関数の定義
パターンマッチ ――データの構造を見る
直接的な値にマッチさせる
コンストラクタにマッチさせる
複合的なパターンマッチ
パターンマッチの網羅性
asパターン
プレースホルダ
ガード ――データの性質を見る
網羅的でないガード条件
パターンマッチとガードを組み合わせる
caseとif
Column 「文」と「式」と,その判別
where/let
Column 場合分けの構文糖衣 ――実は,全部case
let式
where節
3.6 再帰関数 ――反復的な挙動を定義する関数
3つの制御構造と,再帰関数の位置付け ――連結,分岐,反復
再帰的定義
関数の再帰的定義
いろいろな再帰関数
length
take/drop
挿入ソート
再帰的な考え方のコツ
再帰の危険性とその対処
Column そんなに再帰して大丈夫か(!?)
3.7 高階関数 ――結果が関数になる関数,引数として関数を要求する関数
高階関数とは?
結果が関数になる関数
引数として関数を要求する関数
高階関数を定義する
いろいろな高階関数
filter
map
zip(zipWith)
foldl/foldr
scanl/scanr
実際に使ってみる ――部分列の列挙
3.8 まとめ
第4章 評価戦略 ――遅延評価と積極評価
4.1 遅延評価を見てみよう ――有効利用した例から,しっかり学ぶ
たらい回し関数(竹内関数)
たらい回し関数の定義
たらい回し関数の実行――C++版
たらい回し関数の実行――Haskell版
たらい回しの省略
無限のデータ
レンジによる無限列
再帰的定義による無限列
フィボナッチ数列,再び
無限に広がる2分木
省略によるエラー耐性
実行時のエラー
最高の実行時エラー対策 ――それは,実行しないこと
平均値
通常の平均値の計算
ちょっと変わった平均値の計算
4.2 評価戦略 ――遅延評価と積極評価のしくみ,メリット/デメリット
評価戦略と遅延評価
簡約
正規形
簡約の順番
順番による結果の違い
積極評価
C言語
遅延評価
最左最外簡約
WHNF(弱冠頭正規形)
サンク
グラフ簡約
積極評価と遅延評価の,利点と欠点
積極評価の利点,遅延評価の欠点
遅延評価の利点,積極評価の欠点
4.3 評価を制御する ――パフォーマンスチューニングのために
評価の進む様子を観察する
サンクを潰す
コンストラクト時に潰す
束縛時に潰す ――BangPatterns
モジュール単位で潰す ――積極評価のHaskell
サンクを潰したいケース ――スペースリークの恐怖
Haskell版たらい回し関数を遅くする
C++版たらい回し関数を速くする
4.4 まとめ
Column パフォーマンスチューニングの第一歩 ――プロファイルを取る
第5章 モナド ――文脈を持つ計算を扱うための仕掛け
5.1 型クラスをもう一度 ――自分で作るという視点で
型クラスを定義する
型クラスのインスタンスを作る
型クラスインタフェースのデフォルト実装
[比較]他の言語の「あの機能」と「型クラス」
インタフェースの後付け
同じ型であることの保証
5.2 モナドの使い方 ――文脈をうまく扱うための型クラスインタフェース
文脈を持つ計算 ――モナドを使うモチベーション
どこかで失敗するかもしれない計算 ――Maybeモナド
複数の結果を持つ計算 ――リストモナド
同じ環境を参照する計算 ――((->) r)というモナド
型クラスとしてのモナド ――アクション,return(注意!),bind演算子
モナド則 ――インスタンスが満たすべき,3つの性質
「モナド則を満たしていないモナド型クラスのインスタンス」の例,
とHaskellでの注意点
do記法
do記法とモナド則
5.3 いろいろなモナド ――Identity,Maybe,リスト,Reader,Writer,State,IO ...
Identity ――文脈を持たない
Maybe ――失敗の可能性を持っている
現実世界と理想的な型の世界の接続と失敗
リスト ――複数の可能性を持っている
リスト内包表記
文脈の多相性
Reader ――参照できる環境を共有する
configを参照する処理
Writer ――主要な計算の横で,別の値も一直線に合成する
State ――状態の引き継ぎ
IO ――副作用を伴う
副作用を扱えるのに純粋と言える理由
5.4 他の言語におけるモナド ――モナドや,それに類する機能のサポート状況
他の関数型言語とモナド
命令型言語とモナド ――Javaのモナドとの比較
Optionalクラス
Streamクラス
メソッドチェインの弊害 ――do記法のありがたみ
副作用による汚染は防げない
5.5 Haskellプログラムのコンパイル ――コンパイルして,Hello, World!
「普通」の実行方法について ――コンパイルして実行する
5.6 まとめ
Column 関数型言語で飯を喰う
第6章 オススメの開発/設計テクニック ――「関数型/Haskellっぽい」プログラムの設計/実装,考え方
6.1 動作を決める ――テストを書こう
テスト,その前に
テストのためのライブラリ
doctest/QuickCheckによるテスト
doctestの導入
doctestを使う
QuickCheckを併用する
6.2 トップダウンに考える ――問題を大枠で捉え,小さい問題に分割していく
ランレングス圧縮(RLE)
関数の型を決める
テストを書く
「らしからぬ」コード
「らしい」コード
トップダウンに設計/実装する
型から関数を検索する ――型情報で検索できる「hoogle」
設計/実装を進める
hlintで仕上げる ――よりHaskellらしいコードにするために
さらなる仕上げ ――もっとシンプルに
今回の例から学ぶ,設計/実装,考え方の勘所
数独
ソルバの型を考える
盤面状況を扱うデータ構造を決める
何をすると数独が解けるか
まだ数値が埋まっていないマスの候補を選ぶ
マスに入れることのできる数値の候補を選ぶ
入れることのできる数値の候補が最も少ないマスを選ぶ
6.3 制約を設ける ――型に制約を持たせる
制約をどのように表現するか
2の冪乗を要求するインタフェース
2の冪乗という制約を持った数の型
可視性を制御して性質を保護する
命令型言語で型に制約を持たせる ――C++の例
6.4 適切な処理を選ばせる ――型と型クラスを適切に利用し,型に制約を記憶させる
複数のエスケープ
変換履歴を持った文字列の型
変換されているかもしれない文字列のクラス
エスケープ方法の持つべき性質
各エスケープを定義する
モジュールを利用してみる
6.5 より複雑な制約を与える ――とても強力なロジックパズルの例
ロジックパズル ――3人の昼食
人間の推論
推論規則を型で表す
推論規則を使って答えを実装する
強力な型がインタフェース設計に与えた力
6.6 まとめ
第7章 Haskellによるプロダクト開発への道 ――パッケージとの付き合い方
7.1 パッケージの利用 ――パッケージシステムCabal
Haskellのパッケージシステム
公開されているパッケージを探す ――Hackage
公開されているパッケージを利用する ――cabal編
パッケージのインストール
パッケージのアンインストール
パッケージを利用する
公開されているパッケージを利用する ――Cabal sandbox編
sandbox環境を使う
7.2 パッケージの作成 ――とりあえずパッケージングしておこう
cabalize ――パッケージング作業
サンプルパッケージの作成 ――FizzBuzzライブラリ
cabalizeする
オススメのディレクトリ構成
モジュールの作成と公開
ビルド
パッケージング
バージョニング
パッケージの作成方法
パッケージのアップロード,バージョンアップ
Column Hackageへ公開しよう
7.3 組織内開発パッケージの扱い ――工夫,あれこれ
Cabalを通した利用 ――一番単純な方法
Cabal sandboxを通した利用 ――パッケージデータベースを共有しない方法
組織内Hackageサーバの利用
パッケージを分けない
7.4 利用するパッケージの選定 ――依存関係地獄,選定の指針
依存関係地獄
Haskellにおける依存関係地獄
パッケージ選定上,有望な性質
コアに近いパッケージ
枯れたパッケージ
シンプルなパッケージ
依存関係が少ないパッケージ
Column 「バージョン上限」を設ける利点
依存関係が広いパッケージ
Column Cabal sandboxの光と影 ――「パッケージレベルでの組み合わせやすさ」は,いかに?
インタフェースが安定しているパッケージ
7.5 依存パッケージのバージョンコントロール ――パッケージごとにどのバージョンを選択するか
バージョンの選定および固定について
各OSのパッケージシステムに用意されているものを使う
Cabalでローリングアップデートポリシーを定めて逐次更新していく
Stackageに用意されているものを使う
7.6 バージョン間差の吸収 ――バージョン間の差分の検出から
複数開発環境の共存
Dockerを使う
Stackを使う
CIサービスを使う
インタフェースが安定しないパッケージの扱い方
Column Stackage/Stackを使う上での注意
7.7 まとめ
Column HaskellでのWebアプリケーション作成 ――より一層,複雑な文脈を表現するモナドの必要性
第8章 各言語に見られる関数プログラミングの影響 ――Ruby,Python,Java,JavaScript,Go,Swift,Rust,C#,C++
8.1 変数を定数化できるか ――変更を抑止する
変数を定数修飾する
メソッドを定数修飾する
8.2 関数の扱いやすさ ――関数/ラムダ式,変数への代入,関数合成,部分適用,演算子
各言語における関数/ラムダ式
変数への代入
呼び出し方の差異 ――Rubyの例
[関数ではない点1]Pythonのラムダ式 ――参照する環境の影響
[関数ではない点2]Javaのラムダ式 ――定数制約の検査
[関数ではない点3]C++のラムダ式 ――キャプチャ
「関数」を定義するポイント
関数合成
部分適用
Rubyの部分適用
C++,Pythonの部分適用
Goの部分適用
演算子
Swiftの演算子定義 ――オペレータ関数
Rubyの演算子定義
8.3 データ型定義とパターンマッチ ――Rust,Swift
データ型定義とパターンマッチの例
網羅性検査
再帰的な構造
8.4 型システムの強化 ――静的型付けと型検査,型推論
静的型付けの導入
型推論の採用
8.5 リスト内包表記 ――Python,C#のLINQ
Pythonのリスト内包表記
C#のリスト内包表記 ――LINQ
8.6 モナド ――Java 8,Swift
Swiftのモナド相当のインタフェース
Column Python関数プログラミングHOWTO
Stateモナド相当の実装例
8.7 コンパイル時計算 ――C++テンプレート
C++テンプレートによる関数プログラミング
C++テンプレートによるデータ型定義
自然数
リスト
C++テンプレートによる関数定義
C++テンプレートの評価戦略
C++テンプレートの限界
8.8 まとめ
Column Safe navigation operator
Appendix
A.1 関数型言語が使えるプログラミングコンテストサイト ――ゲーム感覚で挑戦
[入門]プログラミングコンテスト
Anarchy Golf
AtCoder
CodeChef
Codeforces
SPOJ
A.2 押さえておきたい参考文献&参考情報 ――次の1手。さらに深い世界へ...
関数プログラミングについて
Haskellについて
型システムについて
圏論について
出版社
正誤表
サポートページ:[増補改訂]関数プログラミング実践入門──簡潔で,正しいコードを書くために:|技術評論社
著者
大川徳之(おおかわのりゆき)
東京大学計数工学科数理情報工学コース,東京大学大学院情報理工学系研究科数理情報学専攻,卒業。
キヤノンソフトウェア㈱を経て,㈱朝日ネットにて,HaskellでのWebアプリケーション開発や,開発環境/インフラの構成管理などに携わっていた。
そろそろどうにかしてAgdaで仕事ができないものか虎視眈々と隙を窺っている。
書評
この本は、Haskellの知識が体系的にまとめられているので、要点をつかむには都合の良い本だと思います。
第1章と第8章は、Haskell以外のプログラミング言語について説明している箇所なので、時間が無ければ読まなくてもOK(読むとしても後回しでOK)。
ただ、第8章の他言語での関数型プログラミングの実践方法については、参考になる部分が多々あります。
要するに、「関数がファーストクラスオブジェクト」の言語なら、どの言語でも関数型プログラミングを実践できるということです。
Haskellで学んだ関数型プログラミングの知識を、他言語でも活用していきたいと思います。