Haskell による関数型デザイン¶
概要¶
本セクションでは、Robert C. Martin の「Functional Design: Principles, Patterns, and Practices」で紹介されている関数型プログラミングの原則とパターンを Haskell で実装します。
Haskell は純粋関数型言語として、関数型プログラミングの概念を最も直接的に表現できる言語の一つです。遅延評価、強い型システム、型クラスなどの特徴により、抽象度の高いコードを安全に記述できます。
構成¶
第1部:基礎原則(Chapters 1-3)¶
- 不変性とデータ変換 - レコード、構造共有、パイプライン
- 関数合成と高階関数 - (.)、(>>>)、カリー化、部分適用
- 多態性とディスパッチ - ADT、型クラス、パターンマッチ
第2部:仕様とテスト(Chapters 4-6)¶
- データバリデーション - カスタムバリデータ、Applicative スタイル
- プロパティベーステスト - QuickCheck
- TDD と関数型 - HSpec によるテスト駆動開発
第3部:構造パターン(Chapters 7-9)¶
- Composite パターン - 再帰的データ構造
- Decorator パターン - 関数ラッパー
- Adapter パターン - 型変換、newtypes
第4部:振る舞いパターン(Chapters 10-14)¶
- Strategy パターン - 高階関数による戦略切り替え
- Command パターン - データとしてのコマンド
- Visitor パターン - Foldable/Traversable
- Abstract Factory パターン - 型クラスによる抽象化
- Abstract Server パターン - モジュラー設計
第5部:実践アプリケーション(Chapters 15-19)¶
- ゴシップ好きなバスの運転手 - シミュレーション
- 給与計算システム - ビジネスロジック
- レンタルビデオシステム - ドメインモデリング
- 並行処理システム - STM、async
- Wa-Tor シミュレーション - セルオートマトン
第6部:ベストプラクティス(Chapters 20-22)¶
- パターン間の相互作用 - パターンの組み合わせ
- ベストプラクティス - イディオム、スタイル
- OO から FP への移行 - リファクタリング戦略
Haskell の特徴¶
純粋性と参照透過性¶
-- 純粋関数:同じ入力には常に同じ出力
calculateArea :: Double -> Double -> Double
calculateArea width height = width * height
-- IO はモナドで明示的に扱う
main :: IO ()
main = do
putStrLn "Enter width:"
w <- readLn
putStrLn "Enter height:"
h <- readLn
print (calculateArea w h)
代数的データ型(ADT)¶
-- 直和型(Sum Type)
data Shape
= Circle Double
| Rectangle Double Double
| Triangle Double Double
deriving (Show, Eq)
-- パターンマッチで網羅的に処理
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle b h) = b * h / 2
型クラス¶
-- アドホック多相性を実現
class Drawable a where
draw :: a -> String
boundingBox :: a -> (Double, Double, Double, Double)
instance Drawable Shape where
draw (Circle r) = "Circle with radius " ++ show r
draw (Rectangle w h) = "Rectangle " ++ show w ++ "x" ++ show h
draw (Triangle b h) = "Triangle with base " ++ show b
boundingBox (Circle r) = (-r, -r, r, r)
boundingBox (Rectangle w h) = (0, 0, w, h)
boundingBox (Triangle b h) = (0, 0, b, h)
関数合成¶
-- (.) で右から左へ合成
process :: String -> Int
process = length . filter isDigit . map toLower
-- (>>>) で左から右へ合成(Control.Arrow)
processArrow :: String -> Int
processArrow = map toLower >>> filter isDigit >>> length
-- パイプライン演算子(& from Data.Function)
result = "Hello123"
& map toLower
& filter isDigit
& length
開発環境¶
必要なツール¶
- GHC 9.4 以上
- Cabal または Stack
- HLS(Haskell Language Server)
Nix を使用する場合¶
nix develop .#haskell
プロジェクト構造¶
apps/haskell/
├── part1/ # 第1部:基礎原則
│ ├── functional-design-part1.cabal
│ ├── src/
│ │ ├── Immutability.hs
│ │ ├── FunctionComposition.hs
│ │ └── Polymorphism.hs
│ └── test/
│ ├── Spec.hs
│ ├── ImmutabilitySpec.hs
│ ├── FunctionCompositionSpec.hs
│ └── PolymorphismSpec.hs
├── part2/ # 第2部:仕様とテスト
├── part3/ # 第3部:構造パターン
├── part4/ # 第4部:振る舞いパターン
├── part5/ # 第5部:実践
├── part6/ # 第6部:並行処理
└── part7/ # 第7部:ベストプラクティス
ビルドとテスト¶
cd apps/haskell/part1
cabal build
cabal test --test-show-details=streaming