Haskell勉強会

関数型プログラミングの学習日記

[増補改訂] 関数プログラミング実践入門

関数プログラミング実践入門」というHaskellの本を買いました。

この本は、2016年に改訂版が出ているので、間違って古いほうを買わないように気を付けてください。

 

 

目次

本書について

本書の構成

増補改訂における,おもな変更点

本書で必要となる前提知識

謝辞

第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++JavaJavaScriptRubyPython,そして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章 各言語に見られる関数プログラミングの影響 ――RubyPythonJavaJavaScript,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 リスト内包表記 ――PythonC#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について
  型システムについて
  圏論について

 

出版社

gihyo.jp

 

正誤表

サポートページ:[増補改訂]関数プログラミング実践入門──簡潔で,正しいコードを書くために:|技術評論社

 

著者

川徳之(おおかわのりゆき)

東京大学計数工学科数理情報工学コース,東京大学大学院情報理工学系研究科数理情報学専攻,卒業。

キヤノンソフトウェア㈱を経て,㈱朝日ネットにて,HaskellでのWebアプリケーション開発や,開発環境/インフラの構成管理などに携わっていた。

そろそろどうにかしてAgdaで仕事ができないものか虎視眈々と隙を窺っている。

バージョンアップ毎にだんだんと増えていくGHCコンパイル時間が最近の悩み。

 

notogawa.hatenablog.com

 

書評

この本は、Haskellの知識が体系的にまとめられているので、要点をつかむには都合の良い本だと思います。

 

第1章と第8章は、Haskell以外のプログラミング言語について説明している箇所なので、時間が無ければ読まなくてもOK(読むとしても後回しでOK)。

ただ、第8章の他言語での関数型プログラミングの実践方法については、参考になる部分が多々あります。

要するに、「関数がファーストクラスオブジェクト」の言語なら、どの言語でも関数型プログラミングを実践できるということです。

 

Haskellで学んだ関数型プログラミングの知識を、他言語でも活用していきたいと思います。