@k2works/claude-code-booster 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +42 -42
- package/bin/claude-code-booster +90 -90
- package/lib/assets/.claude/README.md +239 -239
- package/lib/assets/.claude/scripts/generate-inception-deck.mjs +911 -911
- package/lib/assets/.claude/settings.json +11 -11
- package/lib/assets/.claude/skills/ai-agent-guidelines/SKILL.md +111 -111
- package/lib/assets/.claude/skills/analyzing-architecture/SKILL.md +83 -83
- package/lib/assets/.claude/skills/analyzing-business/SKILL.md +95 -95
- package/lib/assets/.claude/skills/analyzing-data-model/SKILL.md +77 -77
- package/lib/assets/.claude/skills/analyzing-domain-model/SKILL.md +117 -88
- package/lib/assets/.claude/skills/analyzing-inception-deck/SKILL.md +84 -84
- package/lib/assets/.claude/skills/analyzing-non-functional/SKILL.md +95 -95
- package/lib/assets/.claude/skills/analyzing-operation/SKILL.md +95 -95
- package/lib/assets/.claude/skills/analyzing-requirements/SKILL.md +91 -91
- package/lib/assets/.claude/skills/analyzing-tech-stack/SKILL.md +101 -101
- package/lib/assets/.claude/skills/analyzing-test-strategy/SKILL.md +89 -89
- package/lib/assets/.claude/skills/analyzing-ui-design/SKILL.md +80 -80
- package/lib/assets/.claude/skills/analyzing-usecases/SKILL.md +72 -72
- package/lib/assets/.claude/skills/creating-adr/SKILL.md +113 -113
- package/lib/assets/.claude/skills/developing-backend/SKILL.md +100 -100
- package/lib/assets/.claude/skills/developing-frontend/SKILL.md +93 -93
- package/lib/assets/.claude/skills/developing-release/SKILL.md +120 -120
- package/lib/assets/.claude/skills/generating-slides/SKILL.md +94 -94
- package/lib/assets/.claude/skills/git-commit/SKILL.md +81 -81
- package/lib/assets/.claude/skills/killing-processes/SKILL.md +44 -44
- package/lib/assets/.claude/skills/operating-backup/SKILL.md +59 -59
- package/lib/assets/.claude/skills/operating-cicd/SKILL.md +54 -54
- package/lib/assets/.claude/skills/operating-deploy/SKILL.md +67 -67
- package/lib/assets/.claude/skills/operating-docs/SKILL.md +219 -219
- package/lib/assets/.claude/skills/operating-provision/SKILL.md +77 -77
- package/lib/assets/.claude/skills/operating-setup/SKILL.md +63 -63
- package/lib/assets/.claude/skills/orchestrating-analysis/SKILL.md +104 -104
- package/lib/assets/.claude/skills/orchestrating-development/SKILL.md +162 -161
- package/lib/assets/.claude/skills/orchestrating-operation/SKILL.md +158 -158
- package/lib/assets/.claude/skills/orchestrating-project/SKILL.md +144 -144
- package/lib/assets/.claude/skills/planning-releases/SKILL.md +119 -119
- package/lib/assets/.claude/skills/syncing-github-project/SKILL.md +151 -151
- package/lib/assets/.claude/skills/tracking-progress/SKILL.md +91 -91
- package/lib/assets/.claude/skills/validating-iteration-plan/SKILL.md +215 -215
- package/lib/assets/.devcontainer/devcontainer.json +34 -34
- package/lib/assets/.env.example +17 -17
- package/lib/assets/.gitattributes +4 -4
- package/lib/assets/.github/workflows/docker-publish.yml +77 -77
- package/lib/assets/.github/workflows/mkdocs.yml +39 -39
- package/lib/assets/AGENTS.md +94 -94
- package/lib/assets/CLAUDE.md +183 -183
- package/lib/assets/README.md +254 -254
- package/lib/assets/docker-compose.yml +33 -33
- package/lib/assets/docs/adr/index.md +10 -10
- package/lib/assets/docs/article/functional-desgin-ppp/all/01-immutability-and-data-transformation.md +475 -475
- package/lib/assets/docs/article/functional-desgin-ppp/all/02-function-composition.md +519 -519
- package/lib/assets/docs/article/functional-desgin-ppp/all/03-polymorphism.md +537 -537
- package/lib/assets/docs/article/functional-desgin-ppp/all/04-data-validation.md +300 -300
- package/lib/assets/docs/article/functional-desgin-ppp/all/05-property-based-testing.md +320 -320
- package/lib/assets/docs/article/functional-desgin-ppp/all/06-tdd-and-functional.md +498 -498
- package/lib/assets/docs/article/functional-desgin-ppp/all/07-composite-pattern.md +298 -298
- package/lib/assets/docs/article/functional-desgin-ppp/all/08-decorator-pattern.md +291 -291
- package/lib/assets/docs/article/functional-desgin-ppp/all/09-adapter-pattern.md +336 -336
- package/lib/assets/docs/article/functional-desgin-ppp/all/10-strategy-pattern.md +303 -303
- package/lib/assets/docs/article/functional-desgin-ppp/all/11-command-pattern.md +286 -286
- package/lib/assets/docs/article/functional-desgin-ppp/all/12-visitor-pattern.md +322 -322
- package/lib/assets/docs/article/functional-desgin-ppp/all/13-abstract-factory-pattern.md +319 -319
- package/lib/assets/docs/article/functional-desgin-ppp/all/14-abstract-server-pattern.md +365 -365
- package/lib/assets/docs/article/functional-desgin-ppp/all/15-gossiping-bus-drivers.md +156 -156
- package/lib/assets/docs/article/functional-desgin-ppp/all/16-payroll-system.md +178 -178
- package/lib/assets/docs/article/functional-desgin-ppp/all/17-video-rental-system.md +312 -312
- package/lib/assets/docs/article/functional-desgin-ppp/all/18-concurrency-system.md +287 -287
- package/lib/assets/docs/article/functional-desgin-ppp/all/19-wa-tor-simulation.md +286 -286
- package/lib/assets/docs/article/functional-desgin-ppp/all/20-pattern-interactions.md +274 -274
- package/lib/assets/docs/article/functional-desgin-ppp/all/21-best-practices.md +294 -294
- package/lib/assets/docs/article/functional-desgin-ppp/all/22-oo-to-fp-migration.md +337 -337
- package/lib/assets/docs/article/functional-desgin-ppp/all/index.md +388 -388
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/01-immutability-and-data-transformation.md +273 -273
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/02-function-composition.md +380 -380
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/03-polymorphism.md +384 -384
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/04-clojure-spec.md +350 -350
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/05-property-based-testing.md +352 -352
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/06-tdd-in-functional.md +383 -383
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/07-composite-pattern.md +529 -529
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/08-decorator-pattern.md +395 -395
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/09-adapter-pattern.md +399 -399
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/10-strategy-pattern.md +485 -485
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/11-command-pattern.md +566 -566
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/12-visitor-pattern.md +567 -567
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/13-abstract-factory-pattern.md +475 -475
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/14-abstract-server-pattern.md +462 -462
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/15-gossiping-bus-drivers.md +325 -325
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/16-payroll-system.md +401 -401
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/17-video-rental-system.md +450 -450
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/18-concurrency-system.md +475 -475
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/19-wator-simulation.md +739 -739
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/20-pattern-interactions.md +567 -567
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/21-best-practices.md +518 -518
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/22-oo-to-fp-migration.md +532 -532
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/index.md +241 -241
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/01-immutability-and-data-transformation.md +383 -383
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/02-function-composition.md +374 -374
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/03-polymorphism.md +375 -375
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/04-data-validation.md +195 -195
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/05-property-based-testing.md +268 -268
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/06-tdd-and-fp.md +294 -294
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/07-effects-and-pure-functions.md +164 -164
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/08-error-handling-strategies.md +168 -168
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/09-io-and-external-systems.md +254 -254
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/10-concurrency-patterns.md +269 -269
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/11-command-pattern.md +148 -148
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/12-visitor-pattern.md +176 -176
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/13-abstract-factory-pattern.md +604 -604
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/14-abstract-server-pattern.md +729 -729
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/15-gossiping-bus-drivers.md +291 -291
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/16-payroll-system.md +420 -420
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/17-video-rental-system.md +319 -319
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/18-concurrency-system.md +466 -466
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/19-wator-simulation.md +523 -523
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/20-pattern-interactions.md +287 -287
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/21-best-practices.md +340 -340
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/22-oo-to-fp-migration.md +395 -395
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/index.md +248 -248
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/01-immutability-and-data-transformation.md +384 -384
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/02-function-composition.md +452 -452
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/03-polymorphism.md +495 -495
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/04-data-validation.md +416 -416
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/05-property-based-testing.md +382 -382
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/06-tdd-functional.md +687 -687
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/07-composite-pattern.md +442 -442
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/08-decorator-pattern.md +479 -479
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/09-adapter-pattern.md +479 -479
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/10-strategy-pattern.md +427 -427
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/11-command-pattern.md +428 -428
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/12-visitor-pattern.md +339 -339
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/13-abstract-factory-pattern.md +309 -309
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/14-abstract-server-pattern.md +596 -596
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/15-gossiping-bus-drivers.md +355 -355
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/16-payroll-system.md +350 -350
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/17-video-rental-system.md +414 -414
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/18-concurrency-system.md +367 -367
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/19-wator-simulation.md +403 -403
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/20-pattern-interactions.md +291 -291
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/21-best-practices.md +324 -324
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/22-oo-to-fp-migration.md +332 -332
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/index.md +274 -274
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/01-immutability-and-data-transformation.md +298 -298
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/02-function-composition.md +304 -304
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/03-polymorphism.md +362 -362
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/04-data-validation.md +257 -257
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/05-property-based-testing.md +254 -254
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/06-tdd-functional.md +283 -283
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/07-composite-pattern.md +395 -395
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/08-decorator-pattern.md +319 -319
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/09-adapter-pattern.md +382 -382
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/10-strategy-pattern.md +287 -287
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/11-command-pattern.md +303 -303
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/12-visitor-pattern.md +326 -326
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/13-abstract-factory-pattern.md +332 -332
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/14-abstract-server-pattern.md +379 -379
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/15-gossiping-bus-drivers.md +177 -177
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/16-payroll-system.md +219 -219
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/17-video-rental-system.md +244 -244
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/18-concurrency-system.md +363 -363
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/19-wator-simulation.md +438 -438
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/20-pattern-interactions.md +325 -325
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/21-best-practices.md +403 -403
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/22-oo-to-fp-migration.md +469 -469
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/index.md +174 -174
- package/lib/assets/docs/article/functional-desgin-ppp/index.md +90 -90
- package/lib/assets/docs/article/functional-desgin-ppp/rust/01-immutability-and-data-transformation.md +450 -450
- package/lib/assets/docs/article/functional-desgin-ppp/rust/02-function-composition.md +463 -463
- package/lib/assets/docs/article/functional-desgin-ppp/rust/03-polymorphism.md +425 -425
- package/lib/assets/docs/article/functional-desgin-ppp/rust/04-data-validation.md +273 -273
- package/lib/assets/docs/article/functional-desgin-ppp/rust/05-property-based-testing.md +247 -247
- package/lib/assets/docs/article/functional-desgin-ppp/rust/06-tdd-and-functional.md +841 -841
- package/lib/assets/docs/article/functional-desgin-ppp/rust/07-composite-pattern.md +384 -384
- package/lib/assets/docs/article/functional-desgin-ppp/rust/08-decorator-pattern.md +383 -383
- package/lib/assets/docs/article/functional-desgin-ppp/rust/09-adapter-pattern.md +339 -339
- package/lib/assets/docs/article/functional-desgin-ppp/rust/10-strategy-pattern.md +331 -331
- package/lib/assets/docs/article/functional-desgin-ppp/rust/11-command-pattern.md +356 -356
- package/lib/assets/docs/article/functional-desgin-ppp/rust/12-visitor-pattern.md +379 -379
- package/lib/assets/docs/article/functional-desgin-ppp/rust/13-abstract-factory-pattern.md +361 -361
- package/lib/assets/docs/article/functional-desgin-ppp/rust/14-abstract-server-pattern.md +392 -392
- package/lib/assets/docs/article/functional-desgin-ppp/rust/15-gossiping-bus-drivers.md +300 -300
- package/lib/assets/docs/article/functional-desgin-ppp/rust/16-payroll-system.md +297 -297
- package/lib/assets/docs/article/functional-desgin-ppp/rust/17-video-rental-system.md +304 -304
- package/lib/assets/docs/article/functional-desgin-ppp/rust/18-concurrency-system.md +315 -315
- package/lib/assets/docs/article/functional-desgin-ppp/rust/19-wator-simulation.md +311 -311
- package/lib/assets/docs/article/functional-desgin-ppp/rust/20-pattern-interactions.md +304 -304
- package/lib/assets/docs/article/functional-desgin-ppp/rust/21-best-practices.md +336 -336
- package/lib/assets/docs/article/functional-desgin-ppp/rust/22-oo-to-fp-migration.md +349 -349
- package/lib/assets/docs/article/functional-desgin-ppp/rust/index.md +243 -243
- package/lib/assets/docs/article/functional-desgin-ppp/scala/01-immutability-and-data-transformation.md +328 -328
- package/lib/assets/docs/article/functional-desgin-ppp/scala/02-function-composition.md +348 -348
- package/lib/assets/docs/article/functional-desgin-ppp/scala/03-polymorphism.md +357 -357
- package/lib/assets/docs/article/functional-desgin-ppp/scala/04-data-validation.md +364 -364
- package/lib/assets/docs/article/functional-desgin-ppp/scala/05-property-based-testing.md +515 -515
- package/lib/assets/docs/article/functional-desgin-ppp/scala/06-tdd-functional.md +557 -557
- package/lib/assets/docs/article/functional-desgin-ppp/scala/07-composite-pattern.md +363 -363
- package/lib/assets/docs/article/functional-desgin-ppp/scala/08-decorator-pattern.md +327 -327
- package/lib/assets/docs/article/functional-desgin-ppp/scala/09-adapter-pattern.md +517 -517
- package/lib/assets/docs/article/functional-desgin-ppp/scala/10-strategy-pattern.md +441 -441
- package/lib/assets/docs/article/functional-desgin-ppp/scala/11-command-pattern.md +407 -407
- package/lib/assets/docs/article/functional-desgin-ppp/scala/12-visitor-pattern.md +379 -379
- package/lib/assets/docs/article/functional-desgin-ppp/scala/13-abstract-factory-pattern.md +398 -398
- package/lib/assets/docs/article/functional-desgin-ppp/scala/14-abstract-server-pattern.md +476 -476
- package/lib/assets/docs/article/functional-desgin-ppp/scala/15-gossiping-bus-drivers.md +391 -391
- package/lib/assets/docs/article/functional-desgin-ppp/scala/16-payroll-system.md +342 -342
- package/lib/assets/docs/article/functional-desgin-ppp/scala/17-video-rental-system.md +324 -324
- package/lib/assets/docs/article/functional-desgin-ppp/scala/18-concurrency-system.md +730 -730
- package/lib/assets/docs/article/functional-desgin-ppp/scala/19-wator-simulation.md +624 -624
- package/lib/assets/docs/article/functional-desgin-ppp/scala/20-pattern-interactions.md +512 -512
- package/lib/assets/docs/article/functional-desgin-ppp/scala/21-best-practices.md +433 -433
- package/lib/assets/docs/article/functional-desgin-ppp/scala/22-oo-to-fp-migration.md +688 -688
- package/lib/assets/docs/article/functional-desgin-ppp/scala/index.md +243 -243
- package/lib/assets/docs/article/getting-start-tdd/clojure/01-todo-list-and-first-test.md +166 -166
- package/lib/assets/docs/article/getting-start-tdd/clojure/02-fake-it-and-triangulation.md +162 -162
- package/lib/assets/docs/article/getting-start-tdd/clojure/03-obvious-implementation-and-refactoring.md +135 -135
- package/lib/assets/docs/article/getting-start-tdd/clojure/04-version-control-and-conventional-commits.md +88 -88
- package/lib/assets/docs/article/getting-start-tdd/clojure/05-package-management-and-static-analysis.md +299 -299
- package/lib/assets/docs/article/getting-start-tdd/clojure/06-task-runner-and-ci-cd.md +241 -241
- package/lib/assets/docs/article/getting-start-tdd/clojure/07-protocols-and-records.md +131 -131
- package/lib/assets/docs/article/getting-start-tdd/clojure/08-multimethods-and-design-patterns.md +130 -130
- package/lib/assets/docs/article/getting-start-tdd/clojure/09-namespaces-and-module-design.md +127 -127
- package/lib/assets/docs/article/getting-start-tdd/clojure/10-higher-order-functions-and-composition.md +114 -114
- package/lib/assets/docs/article/getting-start-tdd/clojure/11-persistent-data-and-pipeline.md +138 -138
- package/lib/assets/docs/article/getting-start-tdd/clojure/12-error-handling-and-spec.md +161 -161
- package/lib/assets/docs/article/getting-start-tdd/clojure/index.md +65 -65
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter01.md +232 -232
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter02.md +244 -244
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter03.md +202 -202
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter04.md +92 -92
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter05.md +256 -256
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter06.md +195 -195
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter07.md +214 -214
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter08.md +249 -249
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter09.md +174 -174
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter10.md +166 -166
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter11.md +192 -192
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter12.md +211 -211
- package/lib/assets/docs/article/getting-start-tdd/csharp/index.md +83 -83
- package/lib/assets/docs/article/getting-start-tdd/elixir/01-todo-list-and-first-test.md +87 -87
- package/lib/assets/docs/article/getting-start-tdd/elixir/02-fake-it-and-triangulation.md +95 -95
- package/lib/assets/docs/article/getting-start-tdd/elixir/03-obvious-implementation-and-refactoring.md +109 -109
- package/lib/assets/docs/article/getting-start-tdd/elixir/04-version-control-and-conventional-commits.md +96 -96
- package/lib/assets/docs/article/getting-start-tdd/elixir/05-package-management-and-static-analysis.md +88 -88
- package/lib/assets/docs/article/getting-start-tdd/elixir/06-task-runner-and-ci-cd.md +71 -71
- package/lib/assets/docs/article/getting-start-tdd/elixir/07-structs-and-protocols.md +110 -110
- package/lib/assets/docs/article/getting-start-tdd/elixir/08-pattern-matching-and-guards.md +108 -108
- package/lib/assets/docs/article/getting-start-tdd/elixir/09-module-design-and-behaviours.md +104 -104
- package/lib/assets/docs/article/getting-start-tdd/elixir/10-higher-order-functions-and-pipeline.md +178 -178
- package/lib/assets/docs/article/getting-start-tdd/elixir/11-stream-and-lazy-evaluation.md +142 -142
- package/lib/assets/docs/article/getting-start-tdd/elixir/12-error-handling-and-with.md +145 -145
- package/lib/assets/docs/article/getting-start-tdd/elixir/index.md +35 -35
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter01.md +202 -202
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter02.md +246 -246
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter03.md +218 -218
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter04.md +179 -179
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter05.md +267 -267
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter06.md +190 -190
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter07.md +161 -161
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter08.md +175 -175
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter09.md +222 -222
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter10.md +189 -189
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter11.md +212 -212
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter12.md +215 -215
- package/lib/assets/docs/article/getting-start-tdd/fsharp/index.md +71 -71
- package/lib/assets/docs/article/getting-start-tdd/go/01-todo-list-and-first-test.md +213 -213
- package/lib/assets/docs/article/getting-start-tdd/go/02-fake-it-and-triangulation.md +302 -302
- package/lib/assets/docs/article/getting-start-tdd/go/03-obvious-implementation-and-refactoring.md +339 -339
- package/lib/assets/docs/article/getting-start-tdd/go/04-version-control-and-conventional-commits.md +112 -112
- package/lib/assets/docs/article/getting-start-tdd/go/05-package-management-and-static-analysis.md +272 -272
- package/lib/assets/docs/article/getting-start-tdd/go/06-task-runner-and-ci-cd.md +233 -233
- package/lib/assets/docs/article/getting-start-tdd/go/07-encapsulation-and-polymorphism.md +394 -394
- package/lib/assets/docs/article/getting-start-tdd/go/08-design-patterns.md +422 -422
- package/lib/assets/docs/article/getting-start-tdd/go/09-solid-principles-and-module-design.md +400 -400
- package/lib/assets/docs/article/getting-start-tdd/go/10-higher-order-functions-and-composition.md +226 -226
- package/lib/assets/docs/article/getting-start-tdd/go/11-immutable-data-and-pipeline.md +296 -296
- package/lib/assets/docs/article/getting-start-tdd/go/12-error-handling-and-type-safety.md +411 -411
- package/lib/assets/docs/article/getting-start-tdd/go/index.md +83 -83
- package/lib/assets/docs/article/getting-start-tdd/haskell/01-todo-list-and-first-test.md +279 -279
- package/lib/assets/docs/article/getting-start-tdd/haskell/02-fake-it-and-triangulation.md +337 -337
- package/lib/assets/docs/article/getting-start-tdd/haskell/03-obvious-implementation-and-refactoring.md +257 -257
- package/lib/assets/docs/article/getting-start-tdd/haskell/04-version-control-and-conventional-commits.md +182 -182
- package/lib/assets/docs/article/getting-start-tdd/haskell/05-package-management-and-static-analysis.md +313 -313
- package/lib/assets/docs/article/getting-start-tdd/haskell/06-task-runner-and-ci-cd.md +309 -309
- package/lib/assets/docs/article/getting-start-tdd/haskell/07-algebraic-data-types-and-type-classes.md +412 -412
- package/lib/assets/docs/article/getting-start-tdd/haskell/08-pattern-matching-and-guards.md +390 -390
- package/lib/assets/docs/article/getting-start-tdd/haskell/09-module-design-and-smart-constructors.md +461 -461
- package/lib/assets/docs/article/getting-start-tdd/haskell/10-higher-order-functions-and-currying.md +434 -434
- package/lib/assets/docs/article/getting-start-tdd/haskell/11-function-composition-and-point-free.md +392 -392
- package/lib/assets/docs/article/getting-start-tdd/haskell/12-monad-and-error-handling.md +631 -631
- package/lib/assets/docs/article/getting-start-tdd/haskell/index.md +49 -49
- package/lib/assets/docs/article/getting-start-tdd/index.md +93 -93
- package/lib/assets/docs/article/getting-start-tdd/integration/01-language-overview.md +375 -375
- package/lib/assets/docs/article/getting-start-tdd/integration/02-test-framework-comparison.md +349 -349
- package/lib/assets/docs/article/getting-start-tdd/integration/03-tdd-pattern-comparison.md +445 -445
- package/lib/assets/docs/article/getting-start-tdd/integration/04-type-system-comparison.md +409 -409
- package/lib/assets/docs/article/getting-start-tdd/integration/05-dev-environment-comparison.md +330 -330
- package/lib/assets/docs/article/getting-start-tdd/integration/06-learning-roadmap.md +290 -290
- package/lib/assets/docs/article/getting-start-tdd/integration/index.md +69 -69
- package/lib/assets/docs/article/getting-start-tdd/java/01-todo-list-and-first-test.md +234 -234
- package/lib/assets/docs/article/getting-start-tdd/java/02-fake-it-and-triangulation.md +261 -261
- package/lib/assets/docs/article/getting-start-tdd/java/03-obvious-implementation-and-refactoring.md +185 -185
- package/lib/assets/docs/article/getting-start-tdd/java/04-version-control-and-conventional-commits.md +115 -115
- package/lib/assets/docs/article/getting-start-tdd/java/05-package-management-and-static-analysis.md +382 -382
- package/lib/assets/docs/article/getting-start-tdd/java/06-task-runner-and-ci-cd.md +272 -272
- package/lib/assets/docs/article/getting-start-tdd/java/07-encapsulation-and-polymorphism.md +626 -626
- package/lib/assets/docs/article/getting-start-tdd/java/08-design-patterns.md +393 -393
- package/lib/assets/docs/article/getting-start-tdd/java/09-solid-principles-and-module-design.md +310 -310
- package/lib/assets/docs/article/getting-start-tdd/java/10-higher-order-functions-and-composition.md +188 -188
- package/lib/assets/docs/article/getting-start-tdd/java/11-immutable-data-and-pipeline.md +167 -167
- package/lib/assets/docs/article/getting-start-tdd/java/12-error-handling-and-type-safety.md +205 -205
- package/lib/assets/docs/article/getting-start-tdd/java/index.md +61 -61
- package/lib/assets/docs/article/getting-start-tdd/node/01-todo-list-and-first-test.md +244 -244
- package/lib/assets/docs/article/getting-start-tdd/node/02-fake-it-and-triangulation.md +262 -262
- package/lib/assets/docs/article/getting-start-tdd/node/03-obvious-implementation-and-refactoring.md +169 -169
- package/lib/assets/docs/article/getting-start-tdd/node/04-version-control-and-conventional-commits.md +112 -112
- package/lib/assets/docs/article/getting-start-tdd/node/05-package-management-and-static-analysis.md +314 -314
- package/lib/assets/docs/article/getting-start-tdd/node/06-task-runner-and-ci-cd.md +235 -235
- package/lib/assets/docs/article/getting-start-tdd/node/07-encapsulation-and-polymorphism.md +327 -327
- package/lib/assets/docs/article/getting-start-tdd/node/08-design-patterns.md +322 -322
- package/lib/assets/docs/article/getting-start-tdd/node/09-solid-principles-and-module-design.md +285 -285
- package/lib/assets/docs/article/getting-start-tdd/node/10-higher-order-functions-and-composition.md +199 -199
- package/lib/assets/docs/article/getting-start-tdd/node/11-immutable-data-and-pipeline.md +207 -207
- package/lib/assets/docs/article/getting-start-tdd/node/12-error-handling-and-type-safety.md +295 -295
- package/lib/assets/docs/article/getting-start-tdd/node/index.md +56 -56
- package/lib/assets/docs/article/getting-start-tdd/php/01-todo-list-and-first-test.md +259 -259
- package/lib/assets/docs/article/getting-start-tdd/php/02-fake-it-and-triangulation.md +200 -200
- package/lib/assets/docs/article/getting-start-tdd/php/03-obvious-implementation-and-refactoring.md +248 -248
- package/lib/assets/docs/article/getting-start-tdd/php/04-version-control-and-conventional-commits.md +141 -141
- package/lib/assets/docs/article/getting-start-tdd/php/05-package-management-and-static-analysis.md +410 -410
- package/lib/assets/docs/article/getting-start-tdd/php/06-task-runner-and-ci-cd.md +321 -321
- package/lib/assets/docs/article/getting-start-tdd/php/07-encapsulation-and-polymorphism.md +372 -372
- package/lib/assets/docs/article/getting-start-tdd/php/08-design-patterns.md +453 -453
- package/lib/assets/docs/article/getting-start-tdd/php/09-solid-principles-and-module-design.md +460 -460
- package/lib/assets/docs/article/getting-start-tdd/php/10-higher-order-functions-and-composition.md +182 -182
- package/lib/assets/docs/article/getting-start-tdd/php/11-immutable-data-and-pipeline.md +266 -266
- package/lib/assets/docs/article/getting-start-tdd/php/12-error-handling-and-type-safety.md +308 -308
- package/lib/assets/docs/article/getting-start-tdd/php/index.md +84 -84
- package/lib/assets/docs/article/getting-start-tdd/python/01-todo-list-and-first-test.md +201 -201
- package/lib/assets/docs/article/getting-start-tdd/python/02-fake-it-and-triangulation.md +247 -247
- package/lib/assets/docs/article/getting-start-tdd/python/03-obvious-implementation-and-refactoring.md +199 -199
- package/lib/assets/docs/article/getting-start-tdd/python/04-version-control-and-conventional-commits.md +87 -87
- package/lib/assets/docs/article/getting-start-tdd/python/05-package-management-and-static-analysis.md +274 -274
- package/lib/assets/docs/article/getting-start-tdd/python/06-task-runner-and-ci-cd.md +190 -190
- package/lib/assets/docs/article/getting-start-tdd/python/07-encapsulation-and-polymorphism.md +208 -208
- package/lib/assets/docs/article/getting-start-tdd/python/08-design-patterns.md +172 -172
- package/lib/assets/docs/article/getting-start-tdd/python/09-solid-principles-and-module-design.md +130 -130
- package/lib/assets/docs/article/getting-start-tdd/python/10-higher-order-functions-and-composition.md +122 -122
- package/lib/assets/docs/article/getting-start-tdd/python/11-immutable-data-and-pipeline.md +116 -116
- package/lib/assets/docs/article/getting-start-tdd/python/12-error-handling-and-type-safety.md +126 -126
- package/lib/assets/docs/article/getting-start-tdd/python/index.md +55 -55
- package/lib/assets/docs/article/getting-start-tdd/ruby/01-todo-list-and-first-test.md +231 -231
- package/lib/assets/docs/article/getting-start-tdd/ruby/02-fake-it-and-triangulation.md +238 -238
- package/lib/assets/docs/article/getting-start-tdd/ruby/03-obvious-implementation-and-refactoring.md +228 -228
- package/lib/assets/docs/article/getting-start-tdd/ruby/04-version-control-and-conventional-commits.md +112 -112
- package/lib/assets/docs/article/getting-start-tdd/ruby/05-package-management-and-static-analysis.md +287 -287
- package/lib/assets/docs/article/getting-start-tdd/ruby/06-task-runner-and-ci-cd.md +248 -248
- package/lib/assets/docs/article/getting-start-tdd/ruby/07-encapsulation-and-polymorphism.md +279 -279
- package/lib/assets/docs/article/getting-start-tdd/ruby/08-design-patterns.md +329 -329
- package/lib/assets/docs/article/getting-start-tdd/ruby/09-solid-principles-and-module-design.md +196 -196
- package/lib/assets/docs/article/getting-start-tdd/ruby/10-higher-order-functions-and-composition.md +175 -175
- package/lib/assets/docs/article/getting-start-tdd/ruby/11-immutable-data-and-pipeline.md +237 -237
- package/lib/assets/docs/article/getting-start-tdd/ruby/12-error-handling-and-type-safety.md +398 -398
- package/lib/assets/docs/article/getting-start-tdd/ruby/index.md +83 -83
- package/lib/assets/docs/article/getting-start-tdd/rust/01-todo-list-and-first-test.md +211 -211
- package/lib/assets/docs/article/getting-start-tdd/rust/02-fake-it-and-triangulation.md +264 -264
- package/lib/assets/docs/article/getting-start-tdd/rust/03-obvious-implementation-and-refactoring.md +233 -233
- package/lib/assets/docs/article/getting-start-tdd/rust/04-version-control-and-conventional-commits.md +92 -92
- package/lib/assets/docs/article/getting-start-tdd/rust/05-package-management-and-static-analysis.md +212 -212
- package/lib/assets/docs/article/getting-start-tdd/rust/06-task-runner-and-ci-cd.md +164 -164
- package/lib/assets/docs/article/getting-start-tdd/rust/07-encapsulation-and-polymorphism.md +142 -142
- package/lib/assets/docs/article/getting-start-tdd/rust/08-design-patterns.md +145 -145
- package/lib/assets/docs/article/getting-start-tdd/rust/09-solid-principles-and-module-design.md +110 -110
- package/lib/assets/docs/article/getting-start-tdd/rust/10-higher-order-functions-and-composition.md +94 -94
- package/lib/assets/docs/article/getting-start-tdd/rust/11-immutable-data-and-pipeline.md +105 -105
- package/lib/assets/docs/article/getting-start-tdd/rust/12-error-handling-and-type-safety.md +112 -112
- package/lib/assets/docs/article/getting-start-tdd/rust/index.md +83 -83
- package/lib/assets/docs/article/getting-start-tdd/scala/01-todo-list-and-first-test.md +111 -111
- package/lib/assets/docs/article/getting-start-tdd/scala/02-fake-it-and-triangulation.md +107 -107
- package/lib/assets/docs/article/getting-start-tdd/scala/03-obvious-implementation-and-refactoring.md +99 -99
- package/lib/assets/docs/article/getting-start-tdd/scala/04-version-control-and-conventional-commits.md +123 -123
- package/lib/assets/docs/article/getting-start-tdd/scala/05-package-management-and-static-analysis.md +196 -196
- package/lib/assets/docs/article/getting-start-tdd/scala/06-task-runner-and-ci-cd.md +186 -186
- package/lib/assets/docs/article/getting-start-tdd/scala/07-case-classes-and-traits.md +139 -139
- package/lib/assets/docs/article/getting-start-tdd/scala/08-pattern-matching-and-sealed-traits.md +106 -106
- package/lib/assets/docs/article/getting-start-tdd/scala/09-packages-and-module-design.md +75 -75
- package/lib/assets/docs/article/getting-start-tdd/scala/10-higher-order-functions-and-composition.md +104 -104
- package/lib/assets/docs/article/getting-start-tdd/scala/11-collections-and-lazy-evaluation.md +94 -94
- package/lib/assets/docs/article/getting-start-tdd/scala/12-error-handling-and-type-safety.md +92 -92
- package/lib/assets/docs/article/getting-start-tdd/scala/index.md +65 -65
- package/lib/assets/docs/article/grokking-concurrency/all/index.md +404 -404
- package/lib/assets/docs/article/grokking-concurrency/all/part-1-ch02-sequential.md +554 -554
- package/lib/assets/docs/article/grokking-concurrency/all/part-2-ch04-05-threads.md +469 -469
- package/lib/assets/docs/article/grokking-concurrency/all/part-3-ch06-multitasking.md +520 -520
- package/lib/assets/docs/article/grokking-concurrency/all/part-4-ch07-parallel-patterns.md +420 -420
- package/lib/assets/docs/article/grokking-concurrency/all/part-5-ch08-09-synchronization.md +510 -510
- package/lib/assets/docs/article/grokking-concurrency/all/part-6-ch10-11-nonblocking-io.md +435 -435
- package/lib/assets/docs/article/grokking-concurrency/all/part-7-ch12-async.md +465 -465
- package/lib/assets/docs/article/grokking-concurrency/all/part-8-ch13-mapreduce.md +377 -377
- package/lib/assets/docs/article/grokking-concurrency/clojure/index.md +116 -116
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-1.md +108 -108
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-2.md +101 -101
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-3.md +122 -122
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-4.md +123 -123
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-5.md +118 -118
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-6.md +89 -89
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-7.md +100 -100
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-8.md +120 -120
- package/lib/assets/docs/article/grokking-concurrency/csharp/index.md +101 -101
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-1.md +97 -97
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-2.md +123 -123
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-3.md +101 -101
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-4.md +112 -112
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-5.md +99 -99
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-6.md +61 -61
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-7.md +84 -84
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-8.md +92 -92
- package/lib/assets/docs/article/grokking-concurrency/fsharp/index.md +65 -65
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-1.md +80 -80
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-2.md +103 -103
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-3.md +94 -94
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-4.md +110 -110
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-5.md +104 -104
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-6.md +93 -93
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-7.md +121 -121
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-8.md +107 -107
- package/lib/assets/docs/article/grokking-concurrency/haskell/index.md +248 -248
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-1.md +96 -96
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-2.md +96 -96
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-3.md +91 -91
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-4.md +106 -106
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-5.md +99 -99
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-6.md +95 -95
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-7.md +111 -111
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-8.md +118 -118
- package/lib/assets/docs/article/grokking-concurrency/index.md +66 -66
- package/lib/assets/docs/article/grokking-concurrency/java/index.md +102 -102
- package/lib/assets/docs/article/grokking-concurrency/java/part-1.md +308 -308
- package/lib/assets/docs/article/grokking-concurrency/java/part-2.md +334 -334
- package/lib/assets/docs/article/grokking-concurrency/java/part-3.md +221 -221
- package/lib/assets/docs/article/grokking-concurrency/java/part-4.md +213 -213
- package/lib/assets/docs/article/grokking-concurrency/java/part-5.md +112 -112
- package/lib/assets/docs/article/grokking-concurrency/java/part-6.md +69 -69
- package/lib/assets/docs/article/grokking-concurrency/java/part-7.md +101 -101
- package/lib/assets/docs/article/grokking-concurrency/java/part-8.md +101 -101
- package/lib/assets/docs/article/grokking-concurrency/python/index.md +313 -313
- package/lib/assets/docs/article/grokking-concurrency/python/part-1.md +239 -239
- package/lib/assets/docs/article/grokking-concurrency/python/part-2.md +418 -418
- package/lib/assets/docs/article/grokking-concurrency/python/part-3.md +227 -227
- package/lib/assets/docs/article/grokking-concurrency/python/part-4.md +299 -299
- package/lib/assets/docs/article/grokking-concurrency/python/part-5.md +315 -315
- package/lib/assets/docs/article/grokking-concurrency/python/part-6.md +297 -297
- package/lib/assets/docs/article/grokking-concurrency/python/part-7.md +314 -314
- package/lib/assets/docs/article/grokking-concurrency/python/part-8.md +360 -360
- package/lib/assets/docs/article/grokking-concurrency/rust/index.md +270 -270
- package/lib/assets/docs/article/grokking-concurrency/rust/part-1.md +108 -108
- package/lib/assets/docs/article/grokking-concurrency/rust/part-2.md +120 -120
- package/lib/assets/docs/article/grokking-concurrency/rust/part-3.md +126 -126
- package/lib/assets/docs/article/grokking-concurrency/rust/part-4.md +175 -175
- package/lib/assets/docs/article/grokking-concurrency/rust/part-5.md +158 -158
- package/lib/assets/docs/article/grokking-concurrency/rust/part-6.md +94 -94
- package/lib/assets/docs/article/grokking-concurrency/rust/part-7.md +133 -133
- package/lib/assets/docs/article/grokking-concurrency/rust/part-8.md +155 -155
- package/lib/assets/docs/article/grokking-concurrency/scala/index.md +69 -69
- package/lib/assets/docs/article/grokking-concurrency/scala/part-1.md +78 -78
- package/lib/assets/docs/article/grokking-concurrency/scala/part-2.md +112 -112
- package/lib/assets/docs/article/grokking-concurrency/scala/part-3.md +93 -93
- package/lib/assets/docs/article/grokking-concurrency/scala/part-4.md +110 -110
- package/lib/assets/docs/article/grokking-concurrency/scala/part-5.md +119 -119
- package/lib/assets/docs/article/grokking-concurrency/scala/part-6.md +83 -83
- package/lib/assets/docs/article/grokking-concurrency/scala/part-7.md +131 -131
- package/lib/assets/docs/article/grokking-concurrency/scala/part-8.md +129 -129
- package/lib/assets/docs/article/grokkingfp/all/index.md +368 -368
- package/lib/assets/docs/article/grokkingfp/all/part-1-ch01-fp-introduction.md +530 -530
- package/lib/assets/docs/article/grokkingfp/all/part-1-ch02-pure-functions.md +923 -923
- package/lib/assets/docs/article/grokkingfp/all/part-2-ch03-immutable-data.md +1128 -1128
- package/lib/assets/docs/article/grokkingfp/all/part-2-ch04-higher-order-functions.md +1104 -1104
- package/lib/assets/docs/article/grokkingfp/all/part-2-ch05-flatmap.md +1026 -1026
- package/lib/assets/docs/article/grokkingfp/all/part-3-ch06-option.md +785 -785
- package/lib/assets/docs/article/grokkingfp/all/part-3-ch07-either-adt.md +871 -871
- package/lib/assets/docs/article/grokkingfp/all/part-4-ch08-io-monad.md +972 -972
- package/lib/assets/docs/article/grokkingfp/all/part-4-ch09-streams.md +926 -926
- package/lib/assets/docs/article/grokkingfp/all/part-5-ch10-concurrency.md +870 -870
- package/lib/assets/docs/article/grokkingfp/all/part-6-ch11-application.md +715 -715
- package/lib/assets/docs/article/grokkingfp/all/part-6-ch12-testing.md +626 -626
- package/lib/assets/docs/article/grokkingfp/all/writing-plan.md +712 -712
- package/lib/assets/docs/article/grokkingfp/clojure/index.md +276 -276
- package/lib/assets/docs/article/grokkingfp/clojure/part-1.md +667 -667
- package/lib/assets/docs/article/grokkingfp/clojure/part-2.md +643 -643
- package/lib/assets/docs/article/grokkingfp/clojure/part-3.md +620 -620
- package/lib/assets/docs/article/grokkingfp/clojure/part-4.md +697 -697
- package/lib/assets/docs/article/grokkingfp/clojure/part-5.md +751 -751
- package/lib/assets/docs/article/grokkingfp/clojure/part-6.md +721 -721
- package/lib/assets/docs/article/grokkingfp/csharp/index.md +246 -246
- package/lib/assets/docs/article/grokkingfp/csharp/part-1.md +811 -811
- package/lib/assets/docs/article/grokkingfp/csharp/part-2.md +971 -971
- package/lib/assets/docs/article/grokkingfp/csharp/part-3.md +981 -981
- package/lib/assets/docs/article/grokkingfp/csharp/part-4.md +949 -949
- package/lib/assets/docs/article/grokkingfp/csharp/part-5.md +947 -947
- package/lib/assets/docs/article/grokkingfp/csharp/part-6.md +739 -739
- package/lib/assets/docs/article/grokkingfp/elixir/index.md +203 -203
- package/lib/assets/docs/article/grokkingfp/elixir/part-1.md +712 -712
- package/lib/assets/docs/article/grokkingfp/elixir/part-2.md +838 -838
- package/lib/assets/docs/article/grokkingfp/elixir/part-3.md +985 -985
- package/lib/assets/docs/article/grokkingfp/elixir/part-4.md +974 -974
- package/lib/assets/docs/article/grokkingfp/elixir/part-5.md +1286 -1286
- package/lib/assets/docs/article/grokkingfp/elixir/part-6.md +1049 -1049
- package/lib/assets/docs/article/grokkingfp/fsharp/index.md +210 -210
- package/lib/assets/docs/article/grokkingfp/fsharp/part-1.md +714 -714
- package/lib/assets/docs/article/grokkingfp/fsharp/part-2.md +961 -961
- package/lib/assets/docs/article/grokkingfp/fsharp/part-3.md +972 -972
- package/lib/assets/docs/article/grokkingfp/fsharp/part-4.md +832 -832
- package/lib/assets/docs/article/grokkingfp/fsharp/part-5.md +911 -911
- package/lib/assets/docs/article/grokkingfp/fsharp/part-6.md +922 -922
- package/lib/assets/docs/article/grokkingfp/haskell/index.md +234 -234
- package/lib/assets/docs/article/grokkingfp/haskell/part-1.md +591 -591
- package/lib/assets/docs/article/grokkingfp/haskell/part-2.md +866 -866
- package/lib/assets/docs/article/grokkingfp/haskell/part-3.md +915 -915
- package/lib/assets/docs/article/grokkingfp/haskell/part-4.md +878 -878
- package/lib/assets/docs/article/grokkingfp/haskell/part-5.md +845 -845
- package/lib/assets/docs/article/grokkingfp/haskell/part-6.md +844 -844
- package/lib/assets/docs/article/grokkingfp/index.md +143 -143
- package/lib/assets/docs/article/grokkingfp/java/index.md +211 -211
- package/lib/assets/docs/article/grokkingfp/java/part-1.md +648 -648
- package/lib/assets/docs/article/grokkingfp/java/part-2.md +675 -675
- package/lib/assets/docs/article/grokkingfp/java/part-3.md +672 -672
- package/lib/assets/docs/article/grokkingfp/java/part-4.md +771 -771
- package/lib/assets/docs/article/grokkingfp/java/part-5.md +959 -959
- package/lib/assets/docs/article/grokkingfp/java/part-6.md +1328 -1328
- package/lib/assets/docs/article/grokkingfp/python/index.md +258 -258
- package/lib/assets/docs/article/grokkingfp/python/part-1.md +443 -443
- package/lib/assets/docs/article/grokkingfp/python/part-2.md +958 -958
- package/lib/assets/docs/article/grokkingfp/python/part-3.md +1004 -1004
- package/lib/assets/docs/article/grokkingfp/python/part-4.md +765 -765
- package/lib/assets/docs/article/grokkingfp/python/part-5.md +747 -747
- package/lib/assets/docs/article/grokkingfp/python/part-6.md +861 -861
- package/lib/assets/docs/article/grokkingfp/ruby/index.md +330 -330
- package/lib/assets/docs/article/grokkingfp/ruby/part-1.md +755 -755
- package/lib/assets/docs/article/grokkingfp/ruby/part-2.md +938 -938
- package/lib/assets/docs/article/grokkingfp/ruby/part-3.md +946 -946
- package/lib/assets/docs/article/grokkingfp/ruby/part-4.md +921 -921
- package/lib/assets/docs/article/grokkingfp/ruby/part-5.md +908 -908
- package/lib/assets/docs/article/grokkingfp/ruby/part-6.md +1412 -1412
- package/lib/assets/docs/article/grokkingfp/rust/index.md +242 -242
- package/lib/assets/docs/article/grokkingfp/rust/part-1.md +634 -634
- package/lib/assets/docs/article/grokkingfp/rust/part-2.md +1060 -1060
- package/lib/assets/docs/article/grokkingfp/rust/part-3.md +994 -994
- package/lib/assets/docs/article/grokkingfp/rust/part-4.md +573 -573
- package/lib/assets/docs/article/grokkingfp/rust/part-5.md +705 -705
- package/lib/assets/docs/article/grokkingfp/rust/part-6.md +508 -508
- package/lib/assets/docs/article/grokkingfp/scala/index.md +171 -171
- package/lib/assets/docs/article/grokkingfp/scala/part-1.md +543 -543
- package/lib/assets/docs/article/grokkingfp/scala/part-2.md +946 -946
- package/lib/assets/docs/article/grokkingfp/scala/part-3.md +919 -919
- package/lib/assets/docs/article/grokkingfp/scala/part-4.md +742 -742
- package/lib/assets/docs/article/grokkingfp/scala/part-5.md +722 -722
- package/lib/assets/docs/article/grokkingfp/scala/part-6.md +867 -867
- package/lib/assets/docs/article/grokkingfp/typescript/index.md +273 -273
- package/lib/assets/docs/article/grokkingfp/typescript/part-1.md +561 -561
- package/lib/assets/docs/article/grokkingfp/typescript/part-2.md +1129 -1129
- package/lib/assets/docs/article/grokkingfp/typescript/part-3.md +842 -842
- package/lib/assets/docs/article/grokkingfp/typescript/part-4.md +1087 -1087
- package/lib/assets/docs/article/grokkingfp/typescript/part-5.md +717 -717
- package/lib/assets/docs/article/grokkingfp/typescript/part-6.md +982 -982
- package/lib/assets/docs/article/practical-database-design/index.md +121 -121
- package/lib/assets/docs/article/practical-database-design/part1/chapter01.md +288 -288
- package/lib/assets/docs/article/practical-database-design/part1/chapter02.md +518 -518
- package/lib/assets/docs/article/practical-database-design/part1/chapter03.md +557 -557
- package/lib/assets/docs/article/practical-database-design/part2/chapter04.md +924 -924
- package/lib/assets/docs/article/practical-database-design/part2/chapter05.md +1627 -1627
- package/lib/assets/docs/article/practical-database-design/part2/chapter06.md +2716 -2716
- package/lib/assets/docs/article/practical-database-design/part2/chapter07.md +2082 -2082
- package/lib/assets/docs/article/practical-database-design/part2/chapter08.md +2105 -2105
- package/lib/assets/docs/article/practical-database-design/part2/chapter09.md +2031 -2031
- package/lib/assets/docs/article/practical-database-design/part2/chapter10.md +1387 -1387
- package/lib/assets/docs/article/practical-database-design/part2/chapter11.md +1677 -1677
- package/lib/assets/docs/article/practical-database-design/part2/chapter12.md +1417 -1417
- package/lib/assets/docs/article/practical-database-design/part2/chapter13.md +1434 -1434
- package/lib/assets/docs/article/practical-database-design/part3/chapter14.md +667 -667
- package/lib/assets/docs/article/practical-database-design/part3/chapter15.md +1625 -1625
- package/lib/assets/docs/article/practical-database-design/part3/chapter16.md +1915 -1915
- package/lib/assets/docs/article/practical-database-design/part3/chapter17.md +1708 -1708
- package/lib/assets/docs/article/practical-database-design/part3/chapter18.md +2095 -2095
- package/lib/assets/docs/article/practical-database-design/part3/chapter19.md +1123 -1123
- package/lib/assets/docs/article/practical-database-design/part3/chapter20.md +1031 -1031
- package/lib/assets/docs/article/practical-database-design/part3/chapter21.md +1382 -1382
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter14-orm.md +991 -991
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter15-orm.md +1300 -1300
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter16-orm.md +1166 -1166
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter17-orm.md +1584 -1584
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter18-orm.md +1183 -1183
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter19-orm.md +1016 -1016
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter20-orm.md +1753 -1753
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter21-orm.md +1447 -1447
- package/lib/assets/docs/article/practical-database-design/part3-orm/chapter22-orm.md +1878 -1878
- package/lib/assets/docs/article/practical-database-design/part4/chapter22.md +965 -965
- package/lib/assets/docs/article/practical-database-design/part4/chapter23.md +2069 -2069
- package/lib/assets/docs/article/practical-database-design/part4/chapter24.md +2439 -2439
- package/lib/assets/docs/article/practical-database-design/part4/chapter25.md +3661 -3661
- package/lib/assets/docs/article/practical-database-design/part4/chapter26.md +2916 -2916
- package/lib/assets/docs/article/practical-database-design/part4/chapter27.md +3105 -3105
- package/lib/assets/docs/article/practical-database-design/part4/chapter28.md +2697 -2697
- package/lib/assets/docs/article/practical-database-design/part4/chapter29.md +2544 -2544
- package/lib/assets/docs/article/practical-database-design/part4/chapter30.md +2180 -2180
- package/lib/assets/docs/article/practical-database-design/part4/chapter31.md +1192 -1192
- package/lib/assets/docs/article/practical-database-design/part4/chapter32.md +2101 -2101
- package/lib/assets/docs/article/practical-database-design/part5/chapter33.md +1032 -1032
- package/lib/assets/docs/article/practical-database-design/part5/chapter34.md +1609 -1609
- package/lib/assets/docs/article/practical-database-design/part5/chapter35.md +1453 -1453
- package/lib/assets/docs/article/practical-database-design/part5/chapter36.md +1292 -1292
- package/lib/assets/docs/article/practical-database-design/part5/chapter37.md +1470 -1470
- package/lib/assets/docs/article/practical-database-design/part5/chapter38.md +1698 -1698
- package/lib/assets/docs/article/practical-database-design/part5/chapter39.md +2334 -2334
- package/lib/assets/docs/article/practical-database-design/study/study2-1.md +1693 -1693
- package/lib/assets/docs/article/practical-database-design/study/study2-2.md +1347 -1347
- package/lib/assets/docs/article/practical-database-design/study/study2-3.md +2044 -2044
- package/lib/assets/docs/article/practical-database-design/study/study2-4.md +2229 -2229
- package/lib/assets/docs/article/practical-database-design/study/study2-5.md +2418 -2418
- package/lib/assets/docs/article/practical-database-design/study/study3-1.md +2205 -2205
- package/lib/assets/docs/article/practical-database-design/study/study3-2.md +2221 -2221
- package/lib/assets/docs/article/practical-database-design/study/study3-3.md +2253 -2253
- package/lib/assets/docs/article/practical-database-design/study/study3-4.md +2106 -2106
- package/lib/assets/docs/article/practical-database-design/study/study3-5.md +2507 -2507
- package/lib/assets/docs/article/practical-database-design/study/study4-1.md +2587 -2587
- package/lib/assets/docs/article/practical-database-design/study/study4-2.md +2075 -2075
- package/lib/assets/docs/article/practical-database-design/study/study4-3.md +1805 -1805
- package/lib/assets/docs/article/practical-database-design/study/study4-4.md +1895 -1895
- package/lib/assets/docs/article/practical-database-design/study/study4-5.md +2878 -2878
- package/lib/assets/docs/assets/css/extra.css +29 -29
- package/lib/assets/docs/assets/js/extra.js +44 -44
- package/lib/assets/docs/development/index.md +39 -39
- package/lib/assets/docs/operation/index.md +11 -11
- package/lib/assets/docs/reference/CodexCLIMCP/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/351/226/213/347/231/272/343/203/225/343/203/255/343/203/274.md +532 -532
- package/lib/assets/docs/reference/CodexCLIMCP/343/202/265/343/203/274/343/203/220/343/203/274/350/250/255/345/256/232/346/211/213/351/240/206.md +341 -341
- package/lib/assets/docs/reference/Java/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/347/222/260/345/242/203/346/247/213/347/257/211/343/202/254/343/202/244/343/203/211.md +581 -580
- package/lib/assets/docs/reference/SonarQube/343/203/255/343/203/274/343/202/253/343/203/253/347/222/260/345/242/203/343/202/273/343/203/203/343/203/210/343/202/242/343/203/203/343/203/227/346/211/213/351/240/206/346/233/270.md +642 -642
- package/lib/assets/docs/reference/TypeScript/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/347/222/260/345/242/203/346/247/213/347/257/211/343/202/254/343/202/244/343/203/211.md +465 -465
- package/lib/assets/docs/reference/UI/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +450 -450
- package/lib/assets/docs/reference/images/Ansoff.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/BrandBasicStrategy.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/BrandCategorization.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/BrandRecurutementStrategy.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/BrandValue.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/BusinessActivitiy.svg +3 -3
- package/lib/assets/docs/reference/images/HRM.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/MarketingStructure.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/OrganizationElemnts.svg +3 -3
- package/lib/assets/docs/reference/images/PPM.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/PositioningMap.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/ProductLayer.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/ProductMix.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/SWOT.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/TargetMarket.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/ThreeGenericStrategies.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/VRIO.drawio.svg +3 -3
- package/lib/assets/docs/reference/images/ValueChain.drawio.svg +3 -3
- package/lib/assets/docs/reference/index.md +52 -52
- package/lib/assets/docs/reference//343/202/210/343/201/204/343/202/275/343/203/225/343/203/210/343/202/246/343/202/247/343/202/242/343/201/250/343/201/257.md +250 -242
- package/lib/assets/docs/reference//343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +2216 -2216
- package/lib/assets/docs/reference//343/202/244/343/203/263/343/203/225/343/203/251/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +1878 -1878
- package/lib/assets/docs/reference//343/202/250/343/202/257/343/202/271/343/203/210/343/203/252/343/203/274/343/203/240/343/203/227/343/203/255/343/202/260/343/203/251/343/203/237/343/203/263/343/202/260.md +550 -544
- package/lib/assets/docs/reference//343/202/263/343/203/274/343/203/207/343/202/243/343/203/263/343/202/260/343/201/250/343/203/206/343/202/271/343/203/210/343/202/254/343/202/244/343/203/211.md +705 -705
- package/lib/assets/docs/reference//343/203/206/343/202/271/343/203/210/346/210/246/347/225/245/343/202/254/343/202/244/343/203/211.md +1313 -1313
- package/lib/assets/docs/reference//343/203/207/343/203/274/343/202/277/343/203/242/343/203/207/343/203/253/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +311 -311
- package/lib/assets/docs/reference//343/203/211/343/203/241/343/202/244/343/203/263/343/203/242/343/203/207/343/203/253/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +599 -599
- package/lib/assets/docs/reference//343/203/223/343/202/270/343/203/215/343/202/271/343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243/345/210/206/346/236/220/343/202/254/343/202/244/343/203/211.md +528 -528
- package/lib/assets/docs/reference//343/203/246/343/203/274/343/202/271/343/202/261/343/203/274/343/202/271/344/275/234/346/210/220/343/202/254/343/202/244/343/203/211.md +689 -682
- package/lib/assets/docs/reference//343/203/252/343/203/252/343/203/274/343/202/271/343/202/254/343/202/244/343/203/211.md +461 -461
- package/lib/assets/docs/reference//343/203/252/343/203/252/343/203/274/343/202/271/343/203/273/343/202/244/343/203/206/343/203/254/343/203/274/343/202/267/343/203/247/343/203/263/350/250/210/347/224/273/343/202/254/343/202/244/343/203/211.md +580 -560
- package/lib/assets/docs/reference//343/203/255/343/202/270/343/202/253/343/203/253/343/202/267/343/203/263/343/202/255/343/203/263/343/202/260.md +1367 -1367
- package/lib/assets/docs/reference//344/274/201/346/245/255/347/265/214/345/226/266/350/253/226.md +2637 -2636
- package/lib/assets/docs/reference//347/222/260/345/242/203/345/244/211/346/225/260/347/256/241/347/220/206/343/202/254/343/202/244/343/203/211.md +665 -663
- package/lib/assets/docs/reference//350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +1248 -1248
- package/lib/assets/docs/reference//350/250/200/350/252/236/345/210/245/351/226/213/347/231/272/343/202/254/343/202/244/343/203/211.md +28 -0
- package/lib/assets/docs/reference//351/201/213/345/226/266/347/256/241/347/220/206.md +1482 -1482
- package/lib/assets/docs/reference//351/201/213/347/224/250/343/202/271/343/202/257/343/203/252/343/203/227/343/203/210/344/275/234/346/210/220/343/202/254/343/202/244/343/203/211.md +421 -421
- package/lib/assets/docs/reference//351/201/213/347/224/250/350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +392 -392
- package/lib/assets/docs/reference//351/226/213/347/231/272/343/202/254/343/202/244/343/203/211.md +299 -299
- package/lib/assets/docs/reference//351/235/236/346/251/237/350/203/275/350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +1236 -1236
- package/lib/assets/docs/review/index.md +5 -5
- package/lib/assets/docs/strategy/index.md +1 -1
- package/lib/assets/docs/template/ADR.md +30 -30
- package/lib/assets/docs/template/AWS/343/202/271/343/203/206/343/203/274/343/202/270/343/203/263/343/202/260/347/222/260/345/242/203/343/202/273/343/203/203/343/203/210/343/202/242/343/203/203/343/203/227/346/211/213/351/240/206/346/233/270.md +1366 -1366
- package/lib/assets/docs/template/AWS/343/203/227/343/203/255/343/203/200/343/202/257/343/202/267/343/203/247/343/203/263/347/222/260/345/242/203/343/202/273/343/203/203/343/203/210/343/202/242/343/203/203/343/203/227/346/211/213/351/240/206/346/233/270.md +634 -634
- package/lib/assets/docs/template/README.md +50 -50
- package/lib/assets/docs/template/index.md +23 -23
- package/lib/assets/docs/template//343/201/276/343/201/232/343/201/223/343/202/214/343/202/222/350/252/255/343/202/202/343/201/206/343/203/252/343/202/271/343/203/210.md +12 -12
- package/lib/assets/docs/template//343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/351/226/213/347/231/272/347/222/260/345/242/203/343/202/273/343/203/203/343/203/210/343/202/242/343/203/203/343/203/227/346/211/213/351/240/206/346/233/270.md +547 -547
- package/lib/assets/docs/template//343/202/244/343/203/206/343/203/254/343/203/274/343/202/267/343/203/247/343/203/263/345/256/214/344/272/206/345/240/261/345/221/212/346/233/270.md +58 -58
- package/lib/assets/docs/template//343/202/244/343/203/263/343/202/273/343/203/227/343/202/267/343/203/247/343/203/263/343/203/207/343/203/203/343/202/255.md +13 -13
- package/lib/assets/docs/template//343/203/223/343/202/270/343/203/215/343/202/271/343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243.md +379 -379
- package/lib/assets/docs/template//344/274/201/346/245/255/345/210/206/346/236/220.md +573 -573
- package/lib/assets/docs/template//345/256/214/345/205/250/345/275/242/345/274/217/343/201/256/343/203/246/343/203/274/343/202/271/343/202/261/343/203/274/343/202/271.md +69 -68
- package/lib/assets/docs/template//350/246/201/344/273/266/345/256/232/347/276/251.md +669 -669
- package/lib/assets/docs/template//350/250/255/350/250/210.md +173 -173
- package/lib/assets/docs/template//351/226/213/347/231/272/347/222/260/345/242/203/343/202/273/343/203/203/343/203/210/343/202/242/343/203/203/343/203/227/346/211/213/351/240/206/346/233/270.md +688 -688
- package/lib/assets/gulpfile.js +25 -25
- package/lib/assets/mkdocs.yml +136 -135
- package/lib/assets/ops/docker/mkdoc/Dockerfile +19 -19
- package/lib/assets/ops/scripts/journal.js +180 -180
- package/lib/assets/ops/scripts/mkdocs.js +82 -82
- package/lib/assets/ops/scripts/release.js +431 -431
- package/lib/assets/ops/scripts/sonar_local.js +726 -726
- package/lib/assets/ops/scripts/ssh.js +190 -190
- package/lib/assets/ops/scripts/vault.js +299 -299
- package/lib/assets/package-lock.json +1653 -1653
- package/lib/assets/package.json +40 -40
- package/lib/gulpfile.js +37 -37
- package/package.json +41 -41
- package/lib/assets/.claude/agent-memory/xp-programmer/MEMORY.md +0 -6
- package/lib/assets/.claude/agent-memory/xp-programmer/project_cargo_tracker.md +0 -11
- package/lib/assets/.claude/agent-memory/xp-programmer/project_ddd_patterns.md +0 -27
- package/lib/assets/.claude/agent-memory/xp-programmer/project_us07_route_assignment.md +0 -19
|
@@ -1,844 +1,844 @@
|
|
|
1
|
-
# Part VI: 実践的なアプリケーション構築とテスト
|
|
2
|
-
|
|
3
|
-
本章では、これまで学んだ関数型プログラミングの概念を統合し、実践的なアプリケーションを構築します。また、Haskell におけるテスト戦略についても学びます。
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 第12章: テスト戦略と実践アプリケーション
|
|
8
|
-
|
|
9
|
-
### 12.1 TravelGuide アプリケーション
|
|
10
|
-
|
|
11
|
-
**ソースファイル**: `app/haskell/src/Ch12/TestingStrategies.hs`
|
|
12
|
-
|
|
13
|
-
旅行ガイドアプリケーションを例に、実践的な FP アプリケーションの構築方法を学びます。
|
|
14
|
-
|
|
15
|
-
```plantuml
|
|
16
|
-
@startuml
|
|
17
|
-
!theme plain
|
|
18
|
-
|
|
19
|
-
package "TravelGuide Application" {
|
|
20
|
-
rectangle "Model" {
|
|
21
|
-
class Location {
|
|
22
|
-
locId: LocationId
|
|
23
|
-
locName: String
|
|
24
|
-
locPopulation: Int
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class Attraction {
|
|
28
|
-
attrName: String
|
|
29
|
-
attrDescription: Maybe String
|
|
30
|
-
attrLocation: Location
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class TravelGuide {
|
|
34
|
-
tgAttraction: Attraction
|
|
35
|
-
tgSubjects: [String]
|
|
36
|
-
tgSearchReport: SearchReport
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
rectangle "Data Access" {
|
|
41
|
-
class DataAccess {
|
|
42
|
-
+findAttractions()
|
|
43
|
-
+findArtistsFromLocation()
|
|
44
|
-
+findMoviesAboutLocation()
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@enduml
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### 12.2 ドメインモデルの定義
|
|
53
|
-
|
|
54
|
-
```haskell
|
|
55
|
-
-- 位置ID(値オブジェクト)
|
|
56
|
-
newtype LocationId = LocationId { unLocationId :: String }
|
|
57
|
-
deriving (Show, Eq, Ord)
|
|
58
|
-
|
|
59
|
-
-- ロケーション
|
|
60
|
-
data Location = Location
|
|
61
|
-
{ locId :: LocationId
|
|
62
|
-
, locName :: String
|
|
63
|
-
, locPopulation :: Int
|
|
64
|
-
} deriving (Show, Eq)
|
|
65
|
-
|
|
66
|
-
-- アトラクション(観光地)
|
|
67
|
-
data Attraction = Attraction
|
|
68
|
-
{ attrName :: String
|
|
69
|
-
, attrDescription :: Maybe String
|
|
70
|
-
, attrLocation :: Location
|
|
71
|
-
} deriving (Show, Eq)
|
|
72
|
-
|
|
73
|
-
-- 旅行ガイド
|
|
74
|
-
data TravelGuide = TravelGuide
|
|
75
|
-
{ tgAttraction :: Attraction
|
|
76
|
-
, tgSubjects :: [String]
|
|
77
|
-
, tgSearchReport :: SearchReport
|
|
78
|
-
} deriving (Show, Eq)
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Scala との対応:
|
|
82
|
-
|
|
83
|
-
- `opaque type LocationId` → `newtype LocationId`
|
|
84
|
-
- `case class` → `data` with record syntax
|
|
85
|
-
- `Option[String]` → `Maybe String`
|
|
86
|
-
|
|
87
|
-
### 12.3 データアクセス層の抽象化
|
|
88
|
-
|
|
89
|
-
外部データソースへのアクセスをレコード型で抽象化します。
|
|
90
|
-
|
|
91
|
-
```haskell
|
|
92
|
-
-- データアクセスインターフェース
|
|
93
|
-
data DataAccess = DataAccess
|
|
94
|
-
{ findAttractions :: String -> AttractionOrdering -> Int -> IO [Attraction]
|
|
95
|
-
, findArtistsFromLocation :: LocationId -> Int -> IO (Either String [MusicArtist])
|
|
96
|
-
, findMoviesAboutLocation :: LocationId -> Int -> IO (Either String [Movie])
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
```plantuml
|
|
101
|
-
@startuml
|
|
102
|
-
!theme plain
|
|
103
|
-
|
|
104
|
-
rectangle "DataAccess(レコード型)" {
|
|
105
|
-
card "findAttractions"
|
|
106
|
-
card "findArtistsFromLocation"
|
|
107
|
-
card "findMoviesAboutLocation"
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
note bottom
|
|
111
|
-
Haskell ではレコード型で
|
|
112
|
-
インターフェースを表現
|
|
113
|
-
(Scala の trait に相当)
|
|
114
|
-
end note
|
|
115
|
-
|
|
116
|
-
@enduml
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Scala との対応:
|
|
120
|
-
|
|
121
|
-
- `trait DataAccess` → `data DataAccess = DataAccess { ... }`
|
|
122
|
-
- メソッド → レコードのフィールド(関数型)
|
|
123
|
-
|
|
124
|
-
### 12.4 テスト用スタブの作成
|
|
125
|
-
|
|
126
|
-
```haskell
|
|
127
|
-
-- テスト用データアクセスの作成
|
|
128
|
-
mkTestDataAccess :: IO DataAccess
|
|
129
|
-
mkTestDataAccess = return DataAccess
|
|
130
|
-
{ findAttractions = \name _ limit ->
|
|
131
|
-
return $ take limit
|
|
132
|
-
[ Attraction
|
|
133
|
-
{ attrName = "Test Attraction"
|
|
134
|
-
, attrDescription = Just "A test attraction"
|
|
135
|
-
, attrLocation = Location (LocationId "Q123") "Test City" 100000
|
|
136
|
-
}
|
|
137
|
-
| name == "Test" || name == ""
|
|
138
|
-
]
|
|
139
|
-
, findArtistsFromLocation = \_ limit ->
|
|
140
|
-
return $ Right $ take limit [MusicArtist "Test Artist"]
|
|
141
|
-
, findMoviesAboutLocation = \_ limit ->
|
|
142
|
-
return $ Right $ take limit [Movie "Test Movie"]
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
-- 失敗するデータアクセス(エラーテスト用)
|
|
146
|
-
mkFailingDataAccess :: IO DataAccess
|
|
147
|
-
mkFailingDataAccess = return DataAccess
|
|
148
|
-
{ findAttractions = \_ _ limit ->
|
|
149
|
-
return $ take limit [testAttraction]
|
|
150
|
-
, findArtistsFromLocation = \_ _ ->
|
|
151
|
-
return $ Left "Network error"
|
|
152
|
-
, findMoviesAboutLocation = \_ _ ->
|
|
153
|
-
return $ Left "Timeout"
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
```plantuml
|
|
158
|
-
@startuml
|
|
159
|
-
!theme plain
|
|
160
|
-
|
|
161
|
-
rectangle "テスト構成" {
|
|
162
|
-
rectangle "本番" as prod {
|
|
163
|
-
class "WikidataDataAccess" as wda
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
rectangle "テスト" as test {
|
|
167
|
-
class "mkTestDataAccess" as tda
|
|
168
|
-
class "mkFailingDataAccess" as fda
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
note bottom of tda
|
|
173
|
-
テストデータを返す
|
|
174
|
-
return で即座に結果を返す
|
|
175
|
-
end note
|
|
176
|
-
|
|
177
|
-
note bottom of fda
|
|
178
|
-
エラーケースのテスト用
|
|
179
|
-
Left でエラーを返す
|
|
180
|
-
end note
|
|
181
|
-
|
|
182
|
-
@enduml
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### 12.5 SearchReport の導入
|
|
186
|
-
|
|
187
|
-
テスト可能性を高めるため、`SearchReport` を導入します。
|
|
188
|
-
|
|
189
|
-
```haskell
|
|
190
|
-
-- 検索レポート
|
|
191
|
-
data SearchReport = SearchReport
|
|
192
|
-
{ srAttractionsSearched :: Int
|
|
193
|
-
, srErrors :: [String]
|
|
194
|
-
} deriving (Show, Eq)
|
|
195
|
-
|
|
196
|
-
-- 旅行ガイド(SearchReport 付き)
|
|
197
|
-
data TravelGuide = TravelGuide
|
|
198
|
-
{ tgAttraction :: Attraction
|
|
199
|
-
, tgSubjects :: [String]
|
|
200
|
-
, tgSearchReport :: SearchReport
|
|
201
|
-
} deriving (Show, Eq)
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
```plantuml
|
|
205
|
-
@startuml
|
|
206
|
-
!theme plain
|
|
207
|
-
|
|
208
|
-
class TravelGuide {
|
|
209
|
-
tgAttraction: Attraction
|
|
210
|
-
tgSubjects: [String]
|
|
211
|
-
tgSearchReport: SearchReport
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
class SearchReport {
|
|
215
|
-
srAttractionsSearched: Int
|
|
216
|
-
srErrors: [String]
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
TravelGuide --> SearchReport
|
|
220
|
-
|
|
221
|
-
note right of SearchReport
|
|
222
|
-
検索の統計情報と
|
|
223
|
-
エラー情報を保持
|
|
224
|
-
end note
|
|
225
|
-
|
|
226
|
-
@enduml
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### 12.6 アプリケーションロジック
|
|
230
|
-
|
|
231
|
-
```haskell
|
|
232
|
-
-- 旅行ガイドを取得(SearchReport 付き)
|
|
233
|
-
travelGuideWithReport :: DataAccess -> String -> IO (Maybe TravelGuide)
|
|
234
|
-
travelGuideWithReport da attractionName = do
|
|
235
|
-
attractions <- findAttractions da attractionName AttrByLocationPopulation 3
|
|
236
|
-
case attractions of
|
|
237
|
-
[] -> return Nothing
|
|
238
|
-
(attraction:_) -> do
|
|
239
|
-
let locId' = locId $ attrLocation attraction
|
|
240
|
-
artistsResult <- findArtistsFromLocation da locId' 2
|
|
241
|
-
moviesResult <- findMoviesAboutLocation da locId' 2
|
|
242
|
-
|
|
243
|
-
let errors = collectErrors [artistsResult, moviesResult]
|
|
244
|
-
let artists = either (const []) id artistsResult
|
|
245
|
-
let movies = either (const []) id moviesResult
|
|
246
|
-
let subjects = map artistName artists ++ map movieName movies
|
|
247
|
-
|
|
248
|
-
return $ Just TravelGuide
|
|
249
|
-
{ tgAttraction = attraction
|
|
250
|
-
, tgSubjects = subjects
|
|
251
|
-
, tgSearchReport = SearchReport (length attractions) errors
|
|
252
|
-
}
|
|
253
|
-
where
|
|
254
|
-
collectErrors :: [Either String a] -> [String]
|
|
255
|
-
collectErrors = foldr (\r acc -> either (:acc) (const acc) r) []
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
```plantuml
|
|
259
|
-
@startuml
|
|
260
|
-
!theme plain
|
|
261
|
-
|
|
262
|
-
rectangle "travelGuideWithReport 関数" {
|
|
263
|
-
card "1. アトラクション検索" as step1
|
|
264
|
-
card "2. アーティスト検索" as step2
|
|
265
|
-
card "3. 映画検索" as step3
|
|
266
|
-
card "4. エラー収集" as step4
|
|
267
|
-
card "5. TravelGuide 組み立て" as step5
|
|
268
|
-
|
|
269
|
-
step1 --> step2 : attraction
|
|
270
|
-
step1 --> step3 : location.id
|
|
271
|
-
step2 --> step4 : Either
|
|
272
|
-
step3 --> step4 : Either
|
|
273
|
-
step4 --> step5 : errors
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
note bottom
|
|
277
|
-
do 記法で
|
|
278
|
-
複数の IO を合成
|
|
279
|
-
end note
|
|
280
|
-
|
|
281
|
-
@enduml
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### 12.7 キャッシュの実装
|
|
285
|
-
|
|
286
|
-
`IORef` を使用したスレッドセーフなキャッシュの実装:
|
|
287
|
-
|
|
288
|
-
```haskell
|
|
289
|
-
import Data.IORef
|
|
290
|
-
import Data.Map.Strict (Map)
|
|
291
|
-
import qualified Data.Map.Strict as Map
|
|
292
|
-
|
|
293
|
-
-- キャッシュ付きデータアクセス
|
|
294
|
-
type CachedDataAccess = (DataAccess, IORef (Map String [Attraction]))
|
|
295
|
-
|
|
296
|
-
-- キャッシュ付きデータアクセスの作成
|
|
297
|
-
mkCachedDataAccess :: DataAccess -> IO CachedDataAccess
|
|
298
|
-
mkCachedDataAccess da = do
|
|
299
|
-
cache <- newIORef Map.empty
|
|
300
|
-
return (da, cache)
|
|
301
|
-
|
|
302
|
-
-- キャッシュ付きアトラクション検索
|
|
303
|
-
cachedFindAttractions :: CachedDataAccess -> String -> AttractionOrdering -> Int -> IO [Attraction]
|
|
304
|
-
cachedFindAttractions (da, cache) name ordering limit = do
|
|
305
|
-
let key = name ++ "-" ++ show ordering ++ "-" ++ show limit
|
|
306
|
-
cached <- Map.lookup key <$> readIORef cache
|
|
307
|
-
case cached of
|
|
308
|
-
Just attractions -> return attractions -- キャッシュヒット
|
|
309
|
-
Nothing -> do
|
|
310
|
-
attractions <- findAttractions da name ordering limit
|
|
311
|
-
atomicModifyIORef' cache (\m -> (Map.insert key attractions m, ()))
|
|
312
|
-
return attractions
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
```plantuml
|
|
316
|
-
@startuml
|
|
317
|
-
!theme plain
|
|
318
|
-
skinparam activity {
|
|
319
|
-
BackgroundColor White
|
|
320
|
-
BorderColor Black
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
start
|
|
324
|
-
|
|
325
|
-
:キャッシュキー生成;
|
|
326
|
-
|
|
327
|
-
:readIORef でキャッシュ確認;
|
|
328
|
-
|
|
329
|
-
if (キャッシュにデータあり?) then (yes)
|
|
330
|
-
:キャッシュからデータ返却;
|
|
331
|
-
else (no)
|
|
332
|
-
:外部 API を呼び出し;
|
|
333
|
-
:atomicModifyIORef' でキャッシュ保存;
|
|
334
|
-
:結果を返却;
|
|
335
|
-
endif
|
|
336
|
-
|
|
337
|
-
stop
|
|
338
|
-
|
|
339
|
-
note right
|
|
340
|
-
IORef を使用した
|
|
341
|
-
スレッドセーフなキャッシュ
|
|
342
|
-
end note
|
|
343
|
-
|
|
344
|
-
@enduml
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### 12.8 リソース管理(bracket)
|
|
348
|
-
|
|
349
|
-
Haskell の `bracket` を使用して、安全なリソース管理を実現します。
|
|
350
|
-
|
|
351
|
-
```haskell
|
|
352
|
-
import Control.Exception (bracket)
|
|
353
|
-
import System.IO (Handle, openFile, hClose, hGetContents, IOMode(..))
|
|
354
|
-
|
|
355
|
-
-- リソースを安全に使用
|
|
356
|
-
withResource :: IO a -> (a -> IO ()) -> (a -> IO b) -> IO b
|
|
357
|
-
withResource acquire release = bracket acquire release
|
|
358
|
-
|
|
359
|
-
-- ファイルを安全に開く
|
|
360
|
-
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
|
|
361
|
-
withFile' path mode = bracket (openFile path mode) hClose
|
|
362
|
-
|
|
363
|
-
-- 使用例
|
|
364
|
-
readFileContents :: FilePath -> IO (Either String String)
|
|
365
|
-
readFileContents path = do
|
|
366
|
-
result <- try $ withFile' path ReadMode hGetContents
|
|
367
|
-
case result of
|
|
368
|
-
Left e -> return $ Left (show e)
|
|
369
|
-
Right c -> return $ Right c
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
```plantuml
|
|
373
|
-
@startuml
|
|
374
|
-
!theme plain
|
|
375
|
-
|
|
376
|
-
participant "Application" as app
|
|
377
|
-
participant "bracket" as res
|
|
378
|
-
participant "Handle" as handle
|
|
379
|
-
participant "File" as file
|
|
380
|
-
|
|
381
|
-
app -> res: bracket acquire release use
|
|
382
|
-
res -> handle: acquire: openFile
|
|
383
|
-
handle -> file: open
|
|
384
|
-
file --> handle: handle
|
|
385
|
-
|
|
386
|
-
app -> res: use (read contents)
|
|
387
|
-
res -> handle: hGetContents
|
|
388
|
-
handle -> file: read
|
|
389
|
-
file --> handle: contents
|
|
390
|
-
handle --> res: contents
|
|
391
|
-
res --> app: contents
|
|
392
|
-
|
|
393
|
-
app -> res: release (automatic)
|
|
394
|
-
res -> handle: hClose
|
|
395
|
-
handle -> file: close
|
|
396
|
-
|
|
397
|
-
note over res
|
|
398
|
-
bracket は例外が発生しても
|
|
399
|
-
必ず release を実行する
|
|
400
|
-
end note
|
|
401
|
-
|
|
402
|
-
@enduml
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
Scala との対応:
|
|
406
|
-
|
|
407
|
-
- `Resource[IO, A]` → `bracket acquire release use`
|
|
408
|
-
- `Resource.make(acquire)(release)` → `bracket acquire release`
|
|
409
|
-
|
|
410
|
-
### 12.9 純粋関数のテスト
|
|
411
|
-
|
|
412
|
-
純粋関数は副作用がないため、テストが非常に簡単です。
|
|
413
|
-
|
|
414
|
-
```haskell
|
|
415
|
-
-- 人口でロケーションをフィルタリング(純粋関数)
|
|
416
|
-
filterPopularLocations :: [Location] -> Int -> [Location]
|
|
417
|
-
filterPopularLocations locations minPopulation =
|
|
418
|
-
filter (\loc -> locPopulation loc >= minPopulation) locations
|
|
419
|
-
|
|
420
|
-
-- アトラクションを人口でソート(純粋関数)
|
|
421
|
-
sortAttractionsByPopulation :: [Attraction] -> [Attraction]
|
|
422
|
-
sortAttractionsByPopulation =
|
|
423
|
-
sortBy (\a b -> compare (Down $ locPopulation $ attrLocation a)
|
|
424
|
-
(Down $ locPopulation $ attrLocation b))
|
|
425
|
-
|
|
426
|
-
-- バリデーション(純粋関数)
|
|
427
|
-
validateLocation :: Location -> Either String Location
|
|
428
|
-
validateLocation loc
|
|
429
|
-
| null (unLocationId $ locId loc) = Left "Location ID cannot be empty"
|
|
430
|
-
| null (locName loc) = Left "Location name cannot be empty"
|
|
431
|
-
| locPopulation loc < 0 = Left "Population cannot be negative"
|
|
432
|
-
| otherwise = Right loc
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### 12.10 HSpec によるユニットテスト
|
|
436
|
-
|
|
437
|
-
```haskell
|
|
438
|
-
import Test.Hspec
|
|
439
|
-
|
|
440
|
-
spec :: Spec
|
|
441
|
-
spec = do
|
|
442
|
-
describe "filterPopularLocations" $ do
|
|
443
|
-
it "filters by minimum population" $ do
|
|
444
|
-
let locs = [ Location (LocationId "1") "A" 100
|
|
445
|
-
, Location (LocationId "2") "B" 200
|
|
446
|
-
, Location (LocationId "3") "C" 150
|
|
447
|
-
]
|
|
448
|
-
let filtered = filterPopularLocations locs 150
|
|
449
|
-
length filtered `shouldBe` 2
|
|
450
|
-
|
|
451
|
-
it "returns empty for high minimum" $ do
|
|
452
|
-
let locs = [Location (LocationId "1") "A" 100]
|
|
453
|
-
filterPopularLocations locs 1000 `shouldBe` []
|
|
454
|
-
|
|
455
|
-
describe "validateLocation" $ do
|
|
456
|
-
it "validates correct location" $ do
|
|
457
|
-
let loc = Location (LocationId "Q1") "Tokyo" 14000000
|
|
458
|
-
validateLocation loc `shouldBe` Right loc
|
|
459
|
-
|
|
460
|
-
it "rejects empty ID" $ do
|
|
461
|
-
let loc = Location (LocationId "") "Tokyo" 14000000
|
|
462
|
-
validateLocation loc `shouldBe` Left "Location ID cannot be empty"
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
### 12.11 QuickCheck によるプロパティベーステスト
|
|
466
|
-
|
|
467
|
-
QuickCheck を使用したプロパティベーステスト:
|
|
468
|
-
|
|
469
|
-
```haskell
|
|
470
|
-
import Test.QuickCheck
|
|
471
|
-
|
|
472
|
-
-- Arbitrary インスタンスの定義
|
|
473
|
-
instance Arbitrary LocationId where
|
|
474
|
-
arbitrary = LocationId <$> listOf1 (elements ['a'..'z'])
|
|
475
|
-
|
|
476
|
-
instance Arbitrary Location where
|
|
477
|
-
arbitrary = Location
|
|
478
|
-
<$> arbitrary
|
|
479
|
-
<*> listOf1 (elements ['A'..'Z'])
|
|
480
|
-
<*> (abs <$> arbitrary)
|
|
481
|
-
|
|
482
|
-
-- プロパティテスト
|
|
483
|
-
spec :: Spec
|
|
484
|
-
spec = do
|
|
485
|
-
describe "Property-based tests" $ do
|
|
486
|
-
it "filterPopularLocations result size <= input size" $
|
|
487
|
-
property $ \(locs :: [Location]) (minPop :: Int) ->
|
|
488
|
-
length (filterPopularLocations locs (abs minPop)) <= length locs
|
|
489
|
-
|
|
490
|
-
it "filterPopularLocations all results meet minimum" $
|
|
491
|
-
property $ \(locs :: [Location]) (minPop :: Int) ->
|
|
492
|
-
let filtered = filterPopularLocations locs (abs minPop)
|
|
493
|
-
in all (\loc -> locPopulation loc >= abs minPop) filtered
|
|
494
|
-
|
|
495
|
-
it "sortAttractionsByPopulation preserves length" $
|
|
496
|
-
property $ \(attrs :: [Attraction]) ->
|
|
497
|
-
length (sortAttractionsByPopulation attrs) == length attrs
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
```plantuml
|
|
501
|
-
@startuml
|
|
502
|
-
!theme plain
|
|
503
|
-
|
|
504
|
-
rectangle "プロパティベーステスト" {
|
|
505
|
-
card "Arbitrary インスタンス" as gen
|
|
506
|
-
card "ランダムデータ生成" as random
|
|
507
|
-
card "プロパティ検証" as verify
|
|
508
|
-
card "100回以上テスト" as repeat
|
|
509
|
-
|
|
510
|
-
gen --> random
|
|
511
|
-
random --> verify
|
|
512
|
-
verify --> repeat
|
|
513
|
-
repeat --> random : 繰り返し
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
note bottom
|
|
517
|
-
QuickCheck が自動で
|
|
518
|
-
ランダムな入力を生成
|
|
519
|
-
end note
|
|
520
|
-
|
|
521
|
-
@enduml
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
Scala との対応:
|
|
525
|
-
|
|
526
|
-
- ScalaCheck `Gen[A]` → QuickCheck `Arbitrary a`
|
|
527
|
-
- `forAll` → `property`
|
|
528
|
-
|
|
529
|
-
### 12.12 統合テスト
|
|
530
|
-
|
|
531
|
-
```haskell
|
|
532
|
-
describe "Integration tests" $ do
|
|
533
|
-
it "travelGuide returns guide for valid attraction" $ do
|
|
534
|
-
da <- mkTestDataAccess
|
|
535
|
-
guide <- travelGuide da "Test"
|
|
536
|
-
guide `shouldSatisfy` \g -> case g of
|
|
537
|
-
Just g' -> attrName (tgAttraction g') == "Test Attraction"
|
|
538
|
-
Nothing -> False
|
|
539
|
-
|
|
540
|
-
it "travelGuideWithReport collects errors" $ do
|
|
541
|
-
da <- mkFailingDataAccess
|
|
542
|
-
guide <- travelGuideWithReport da "Test"
|
|
543
|
-
case guide of
|
|
544
|
-
Just g -> do
|
|
545
|
-
let errors = srErrors (tgSearchReport g)
|
|
546
|
-
length errors `shouldBe` 2
|
|
547
|
-
errors `shouldContain` ["Network error"]
|
|
548
|
-
Nothing -> expectationFailure "Expected Just"
|
|
549
|
-
|
|
550
|
-
it "still returns guide even with errors" $ do
|
|
551
|
-
da <- mkFailingDataAccess
|
|
552
|
-
guide <- travelGuideWithReport da "Test"
|
|
553
|
-
guide `shouldSatisfy` \g -> case g of
|
|
554
|
-
Just _ -> True
|
|
555
|
-
Nothing -> False
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
### 12.13 テストピラミッド
|
|
559
|
-
|
|
560
|
-
```plantuml
|
|
561
|
-
@startuml
|
|
562
|
-
!theme plain
|
|
563
|
-
|
|
564
|
-
rectangle "テストピラミッド" {
|
|
565
|
-
rectangle "E2E テスト\n(少数)" as e2e
|
|
566
|
-
rectangle "統合テスト\n(中程度)" as integration
|
|
567
|
-
rectangle "単体テスト + プロパティテスト\n(多数)" as unit
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
e2e -[hidden]down- integration
|
|
571
|
-
integration -[hidden]down- unit
|
|
572
|
-
|
|
573
|
-
note right of unit
|
|
574
|
-
Haskell では純粋関数が多いため
|
|
575
|
-
単体テストとプロパティテストが
|
|
576
|
-
非常に効果的
|
|
577
|
-
end note
|
|
578
|
-
|
|
579
|
-
@enduml
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
---
|
|
583
|
-
|
|
584
|
-
## まとめ
|
|
585
|
-
|
|
586
|
-
### Part VI で学んだこと
|
|
587
|
-
|
|
588
|
-
```plantuml
|
|
589
|
-
@startuml
|
|
590
|
-
!theme plain
|
|
591
|
-
|
|
592
|
-
rectangle "Part VI: 実践的なアプリケーション" {
|
|
593
|
-
rectangle "第12章" as ch12 {
|
|
594
|
-
card "ドメインモデル設計"
|
|
595
|
-
card "DataAccess 抽象化"
|
|
596
|
-
card "bracket リソース管理"
|
|
597
|
-
card "IORef キャッシュ"
|
|
598
|
-
card "SearchReport"
|
|
599
|
-
card "HSpec ユニットテスト"
|
|
600
|
-
card "QuickCheck プロパティテスト"
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
@enduml
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
### Scala と Haskell の比較
|
|
608
|
-
|
|
609
|
-
| 概念 | Scala | Haskell |
|
|
610
|
-
|------|-------|---------|
|
|
611
|
-
| インターフェース | `trait` | `data` (レコード型) |
|
|
612
|
-
| 値オブジェクト | `opaque type` | `newtype` |
|
|
613
|
-
| オプショナル | `Option[A]` | `Maybe a` |
|
|
614
|
-
| リソース管理 | `Resource[IO, A]` | `bracket` |
|
|
615
|
-
| キャッシュ | `Ref[IO, Map]` | `IORef (Map ...)` |
|
|
616
|
-
| ユニットテスト | ScalaTest | HSpec |
|
|
617
|
-
| プロパティテスト | ScalaCheck | QuickCheck |
|
|
618
|
-
| ジェネレータ | `Gen[A]` | `Arbitrary a` |
|
|
619
|
-
|
|
620
|
-
### キーポイント
|
|
621
|
-
|
|
622
|
-
1. **抽象化の重要性**: レコード型で外部依存を抽象化
|
|
623
|
-
2. **bracket でリソース管理**: 安全なリソースの取得と解放
|
|
624
|
-
3. **IORef でキャッシュ**: スレッドセーフな状態管理
|
|
625
|
-
4. **Either でエラー処理**: 明示的なエラーハンドリング
|
|
626
|
-
5. **SearchReport**: テスト可能性と可観測性の向上
|
|
627
|
-
6. **スタブ**: 外部依存を差し替えてテスト
|
|
628
|
-
7. **プロパティベーステスト**: QuickCheck でランダム入力による不変条件の検証
|
|
629
|
-
|
|
630
|
-
### 学習の総括
|
|
631
|
-
|
|
632
|
-
```plantuml
|
|
633
|
-
@startuml
|
|
634
|
-
!theme plain
|
|
635
|
-
left to right direction
|
|
636
|
-
|
|
637
|
-
rectangle "FP の学習パス" {
|
|
638
|
-
card "Part I\n基礎" as p1
|
|
639
|
-
card "Part II\nイミュータブル操作" as p2
|
|
640
|
-
card "Part III\n型による安全性" as p3
|
|
641
|
-
card "Part IV\nIO/Stream" as p4
|
|
642
|
-
card "Part V\n並行処理" as p5
|
|
643
|
-
card "Part VI\n実践" as p6
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
p1 --> p2
|
|
647
|
-
p2 --> p3
|
|
648
|
-
p3 --> p4
|
|
649
|
-
p4 --> p5
|
|
650
|
-
p5 --> p6
|
|
651
|
-
|
|
652
|
-
@enduml
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
---
|
|
656
|
-
|
|
657
|
-
## 演習問題
|
|
658
|
-
|
|
659
|
-
### 問題 1: DataAccess の拡張
|
|
660
|
-
|
|
661
|
-
以下の要件で `DataAccess` を拡張してください:
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
- 新しいフィールド `findHotelsNearLocation` を追加
|
|
665
|
-
- 戻り値は `IO (Either String [Hotel])`
|
|
666
|
-
|
|
667
|
-
```haskell
|
|
668
|
-
data Hotel = Hotel
|
|
669
|
-
{ hotelName :: String
|
|
670
|
-
, hotelRating :: Double
|
|
671
|
-
, hotelLocation :: Location
|
|
672
|
-
} deriving (Show, Eq)
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
<details>
|
|
676
|
-
<summary>解答</summary>
|
|
677
|
-
|
|
678
|
-
```haskell
|
|
679
|
-
data DataAccess = DataAccess
|
|
680
|
-
{ findAttractions :: String -> AttractionOrdering -> Int -> IO [Attraction]
|
|
681
|
-
, findArtistsFromLocation :: LocationId -> Int -> IO (Either String [MusicArtist])
|
|
682
|
-
, findMoviesAboutLocation :: LocationId -> Int -> IO (Either String [Movie])
|
|
683
|
-
, findHotelsNearLocation :: LocationId -> Int -> IO (Either String [Hotel])
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
-- テスト用スタブ
|
|
687
|
-
mkTestDataAccess :: IO DataAccess
|
|
688
|
-
mkTestDataAccess = return DataAccess
|
|
689
|
-
{ -- 既存の実装...
|
|
690
|
-
, findHotelsNearLocation = \_ limit ->
|
|
691
|
-
return $ Right $ take limit
|
|
692
|
-
[Hotel "Test Hotel" 4.5 (Location (LocationId "Q123") "Test City" 100000)]
|
|
693
|
-
}
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
</details>
|
|
697
|
-
|
|
698
|
-
### 問題 2: プロパティベーステスト
|
|
699
|
-
|
|
700
|
-
以下の関数に対するプロパティベーステストを書いてください:
|
|
701
|
-
|
|
702
|
-
```haskell
|
|
703
|
-
combineSubjects :: [MusicArtist] -> [Movie] -> [String]
|
|
704
|
-
combineSubjects artists movies =
|
|
705
|
-
map artistName artists ++ map movieName movies
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
<details>
|
|
709
|
-
<summary>解答</summary>
|
|
710
|
-
|
|
711
|
-
```haskell
|
|
712
|
-
import Test.QuickCheck
|
|
713
|
-
|
|
714
|
-
instance Arbitrary MusicArtist where
|
|
715
|
-
arbitrary = MusicArtist <$> listOf1 (elements ['A'..'Z'])
|
|
716
|
-
|
|
717
|
-
instance Arbitrary Movie where
|
|
718
|
-
arbitrary = Movie <$> listOf1 (elements ['A'..'Z'])
|
|
719
|
-
|
|
720
|
-
spec :: Spec
|
|
721
|
-
spec = do
|
|
722
|
-
describe "combineSubjects properties" $ do
|
|
723
|
-
it "length is sum of inputs" $
|
|
724
|
-
property $ \(artists :: [MusicArtist]) (movies :: [Movie]) ->
|
|
725
|
-
length (combineSubjects artists movies) == length artists + length movies
|
|
726
|
-
|
|
727
|
-
it "artists come first" $
|
|
728
|
-
property $ \(artists :: [MusicArtist]) (movies :: [Movie]) ->
|
|
729
|
-
let result = combineSubjects artists movies
|
|
730
|
-
artistNames = map artistName artists
|
|
731
|
-
in take (length artists) result == artistNames
|
|
732
|
-
|
|
733
|
-
it "movies come after artists" $
|
|
734
|
-
property $ \(artists :: [MusicArtist]) (movies :: [Movie]) ->
|
|
735
|
-
let result = combineSubjects artists movies
|
|
736
|
-
movieNames = map movieName movies
|
|
737
|
-
in drop (length artists) result == movieNames
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
</details>
|
|
741
|
-
|
|
742
|
-
### 問題 3: bracket の実装
|
|
743
|
-
|
|
744
|
-
データベース接続を安全に管理する関数を実装してください。
|
|
745
|
-
|
|
746
|
-
```haskell
|
|
747
|
-
data Connection = Connection { connId :: Int }
|
|
748
|
-
|
|
749
|
-
-- 実装してください
|
|
750
|
-
withConnection :: IO Connection -> (Connection -> IO a) -> IO a
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
<details>
|
|
754
|
-
<summary>解答</summary>
|
|
755
|
-
|
|
756
|
-
```haskell
|
|
757
|
-
import Control.Exception (bracket)
|
|
758
|
-
|
|
759
|
-
data Connection = Connection { connId :: Int }
|
|
760
|
-
|
|
761
|
-
-- 接続を開く
|
|
762
|
-
openConnection :: IO Connection
|
|
763
|
-
openConnection = do
|
|
764
|
-
putStrLn "Opening connection..."
|
|
765
|
-
return $ Connection 1
|
|
766
|
-
|
|
767
|
-
-- 接続を閉じる
|
|
768
|
-
closeConnection :: Connection -> IO ()
|
|
769
|
-
closeConnection conn = putStrLn $ "Closing connection " ++ show (connId conn)
|
|
770
|
-
|
|
771
|
-
-- 安全に接続を使用
|
|
772
|
-
withConnection :: (Connection -> IO a) -> IO a
|
|
773
|
-
withConnection = bracket openConnection closeConnection
|
|
774
|
-
|
|
775
|
-
-- 使用例
|
|
776
|
-
queryDatabase :: IO String
|
|
777
|
-
queryDatabase = withConnection $ \conn -> do
|
|
778
|
-
putStrLn $ "Querying with connection " ++ show (connId conn)
|
|
779
|
-
return "Query result"
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
</details>
|
|
783
|
-
|
|
784
|
-
### 問題 4: エラー収集
|
|
785
|
-
|
|
786
|
-
複数の `Either` からエラーを収集する関数を実装してください。
|
|
787
|
-
|
|
788
|
-
```haskell
|
|
789
|
-
collectAllErrors :: [Either String a] -> [String]
|
|
790
|
-
collectAllErrors = ???
|
|
791
|
-
```
|
|
792
|
-
|
|
793
|
-
<details>
|
|
794
|
-
<summary>解答</summary>
|
|
795
|
-
|
|
796
|
-
```haskell
|
|
797
|
-
collectAllErrors :: [Either String a] -> [String]
|
|
798
|
-
collectAllErrors = foldr collect []
|
|
799
|
-
where
|
|
800
|
-
collect (Left err) acc = err : acc
|
|
801
|
-
collect (Right _) acc = acc
|
|
802
|
-
|
|
803
|
-
-- または
|
|
804
|
-
collectAllErrors :: [Either String a] -> [String]
|
|
805
|
-
collectAllErrors results = [err | Left err <- results]
|
|
806
|
-
|
|
807
|
-
-- テスト
|
|
808
|
-
-- collectAllErrors [Right 1, Left "Error1", Right 2, Left "Error2"]
|
|
809
|
-
-- => ["Error1", "Error2"]
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
</details>
|
|
813
|
-
|
|
814
|
-
---
|
|
815
|
-
|
|
816
|
-
## シリーズ全体の総括
|
|
817
|
-
|
|
818
|
-
本シリーズでは、「Grokking Functional Programming」の内容に沿って、Haskell で関数型プログラミングの基礎から実践的なアプリケーション構築までを学びました。
|
|
819
|
-
|
|
820
|
-
### 学んだ主な概念
|
|
821
|
-
|
|
822
|
-
| Part | 章 | 主な概念 |
|
|
823
|
-
|------|-----|----------|
|
|
824
|
-
| I | 1-2 | 純粋関数、参照透過性、型推論 |
|
|
825
|
-
| II | 3-5 | イミュータブルデータ、高階関数、do 記法 |
|
|
826
|
-
| III | 6-7 | Maybe、Either、パターンマッチング |
|
|
827
|
-
| IV | 8-9 | IO モナド、遅延リスト(無限ストリーム) |
|
|
828
|
-
| V | 10-11 | 並行処理、IORef、Async、STM |
|
|
829
|
-
| VI | 12 | 実践アプリケーション、テスト戦略 |
|
|
830
|
-
|
|
831
|
-
### Haskell の関数型プログラミングの利点
|
|
832
|
-
|
|
833
|
-
1. **純粋性の保証**: 型システムが純粋関数と IO を分離
|
|
834
|
-
2. **遅延評価**: 無限リストが自然に扱える
|
|
835
|
-
3. **強力な型システム**: newtype、ADT で安全なモデリング
|
|
836
|
-
4. **STM**: デッドロックフリーな並行処理
|
|
837
|
-
5. **QuickCheck**: プロパティベーステストの先駆者
|
|
838
|
-
|
|
839
|
-
### 次のステップ
|
|
840
|
-
|
|
841
|
-
- Monad Transformers(モナドトランスフォーマー)を学ぶ
|
|
842
|
-
- lens ライブラリでデータ操作を効率化
|
|
843
|
-
- servant で型安全な Web API を構築
|
|
844
|
-
- 実際のプロジェクトで Haskell を適用する
|
|
1
|
+
# Part VI: 実践的なアプリケーション構築とテスト
|
|
2
|
+
|
|
3
|
+
本章では、これまで学んだ関数型プログラミングの概念を統合し、実践的なアプリケーションを構築します。また、Haskell におけるテスト戦略についても学びます。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 第12章: テスト戦略と実践アプリケーション
|
|
8
|
+
|
|
9
|
+
### 12.1 TravelGuide アプリケーション
|
|
10
|
+
|
|
11
|
+
**ソースファイル**: `app/haskell/src/Ch12/TestingStrategies.hs`
|
|
12
|
+
|
|
13
|
+
旅行ガイドアプリケーションを例に、実践的な FP アプリケーションの構築方法を学びます。
|
|
14
|
+
|
|
15
|
+
```plantuml
|
|
16
|
+
@startuml
|
|
17
|
+
!theme plain
|
|
18
|
+
|
|
19
|
+
package "TravelGuide Application" {
|
|
20
|
+
rectangle "Model" {
|
|
21
|
+
class Location {
|
|
22
|
+
locId: LocationId
|
|
23
|
+
locName: String
|
|
24
|
+
locPopulation: Int
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class Attraction {
|
|
28
|
+
attrName: String
|
|
29
|
+
attrDescription: Maybe String
|
|
30
|
+
attrLocation: Location
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class TravelGuide {
|
|
34
|
+
tgAttraction: Attraction
|
|
35
|
+
tgSubjects: [String]
|
|
36
|
+
tgSearchReport: SearchReport
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
rectangle "Data Access" {
|
|
41
|
+
class DataAccess {
|
|
42
|
+
+findAttractions()
|
|
43
|
+
+findArtistsFromLocation()
|
|
44
|
+
+findMoviesAboutLocation()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@enduml
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 12.2 ドメインモデルの定義
|
|
53
|
+
|
|
54
|
+
```haskell
|
|
55
|
+
-- 位置ID(値オブジェクト)
|
|
56
|
+
newtype LocationId = LocationId { unLocationId :: String }
|
|
57
|
+
deriving (Show, Eq, Ord)
|
|
58
|
+
|
|
59
|
+
-- ロケーション
|
|
60
|
+
data Location = Location
|
|
61
|
+
{ locId :: LocationId
|
|
62
|
+
, locName :: String
|
|
63
|
+
, locPopulation :: Int
|
|
64
|
+
} deriving (Show, Eq)
|
|
65
|
+
|
|
66
|
+
-- アトラクション(観光地)
|
|
67
|
+
data Attraction = Attraction
|
|
68
|
+
{ attrName :: String
|
|
69
|
+
, attrDescription :: Maybe String
|
|
70
|
+
, attrLocation :: Location
|
|
71
|
+
} deriving (Show, Eq)
|
|
72
|
+
|
|
73
|
+
-- 旅行ガイド
|
|
74
|
+
data TravelGuide = TravelGuide
|
|
75
|
+
{ tgAttraction :: Attraction
|
|
76
|
+
, tgSubjects :: [String]
|
|
77
|
+
, tgSearchReport :: SearchReport
|
|
78
|
+
} deriving (Show, Eq)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Scala との対応:
|
|
82
|
+
|
|
83
|
+
- `opaque type LocationId` → `newtype LocationId`
|
|
84
|
+
- `case class` → `data` with record syntax
|
|
85
|
+
- `Option[String]` → `Maybe String`
|
|
86
|
+
|
|
87
|
+
### 12.3 データアクセス層の抽象化
|
|
88
|
+
|
|
89
|
+
外部データソースへのアクセスをレコード型で抽象化します。
|
|
90
|
+
|
|
91
|
+
```haskell
|
|
92
|
+
-- データアクセスインターフェース
|
|
93
|
+
data DataAccess = DataAccess
|
|
94
|
+
{ findAttractions :: String -> AttractionOrdering -> Int -> IO [Attraction]
|
|
95
|
+
, findArtistsFromLocation :: LocationId -> Int -> IO (Either String [MusicArtist])
|
|
96
|
+
, findMoviesAboutLocation :: LocationId -> Int -> IO (Either String [Movie])
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```plantuml
|
|
101
|
+
@startuml
|
|
102
|
+
!theme plain
|
|
103
|
+
|
|
104
|
+
rectangle "DataAccess(レコード型)" {
|
|
105
|
+
card "findAttractions"
|
|
106
|
+
card "findArtistsFromLocation"
|
|
107
|
+
card "findMoviesAboutLocation"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
note bottom
|
|
111
|
+
Haskell ではレコード型で
|
|
112
|
+
インターフェースを表現
|
|
113
|
+
(Scala の trait に相当)
|
|
114
|
+
end note
|
|
115
|
+
|
|
116
|
+
@enduml
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Scala との対応:
|
|
120
|
+
|
|
121
|
+
- `trait DataAccess` → `data DataAccess = DataAccess { ... }`
|
|
122
|
+
- メソッド → レコードのフィールド(関数型)
|
|
123
|
+
|
|
124
|
+
### 12.4 テスト用スタブの作成
|
|
125
|
+
|
|
126
|
+
```haskell
|
|
127
|
+
-- テスト用データアクセスの作成
|
|
128
|
+
mkTestDataAccess :: IO DataAccess
|
|
129
|
+
mkTestDataAccess = return DataAccess
|
|
130
|
+
{ findAttractions = \name _ limit ->
|
|
131
|
+
return $ take limit
|
|
132
|
+
[ Attraction
|
|
133
|
+
{ attrName = "Test Attraction"
|
|
134
|
+
, attrDescription = Just "A test attraction"
|
|
135
|
+
, attrLocation = Location (LocationId "Q123") "Test City" 100000
|
|
136
|
+
}
|
|
137
|
+
| name == "Test" || name == ""
|
|
138
|
+
]
|
|
139
|
+
, findArtistsFromLocation = \_ limit ->
|
|
140
|
+
return $ Right $ take limit [MusicArtist "Test Artist"]
|
|
141
|
+
, findMoviesAboutLocation = \_ limit ->
|
|
142
|
+
return $ Right $ take limit [Movie "Test Movie"]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
-- 失敗するデータアクセス(エラーテスト用)
|
|
146
|
+
mkFailingDataAccess :: IO DataAccess
|
|
147
|
+
mkFailingDataAccess = return DataAccess
|
|
148
|
+
{ findAttractions = \_ _ limit ->
|
|
149
|
+
return $ take limit [testAttraction]
|
|
150
|
+
, findArtistsFromLocation = \_ _ ->
|
|
151
|
+
return $ Left "Network error"
|
|
152
|
+
, findMoviesAboutLocation = \_ _ ->
|
|
153
|
+
return $ Left "Timeout"
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```plantuml
|
|
158
|
+
@startuml
|
|
159
|
+
!theme plain
|
|
160
|
+
|
|
161
|
+
rectangle "テスト構成" {
|
|
162
|
+
rectangle "本番" as prod {
|
|
163
|
+
class "WikidataDataAccess" as wda
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
rectangle "テスト" as test {
|
|
167
|
+
class "mkTestDataAccess" as tda
|
|
168
|
+
class "mkFailingDataAccess" as fda
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
note bottom of tda
|
|
173
|
+
テストデータを返す
|
|
174
|
+
return で即座に結果を返す
|
|
175
|
+
end note
|
|
176
|
+
|
|
177
|
+
note bottom of fda
|
|
178
|
+
エラーケースのテスト用
|
|
179
|
+
Left でエラーを返す
|
|
180
|
+
end note
|
|
181
|
+
|
|
182
|
+
@enduml
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 12.5 SearchReport の導入
|
|
186
|
+
|
|
187
|
+
テスト可能性を高めるため、`SearchReport` を導入します。
|
|
188
|
+
|
|
189
|
+
```haskell
|
|
190
|
+
-- 検索レポート
|
|
191
|
+
data SearchReport = SearchReport
|
|
192
|
+
{ srAttractionsSearched :: Int
|
|
193
|
+
, srErrors :: [String]
|
|
194
|
+
} deriving (Show, Eq)
|
|
195
|
+
|
|
196
|
+
-- 旅行ガイド(SearchReport 付き)
|
|
197
|
+
data TravelGuide = TravelGuide
|
|
198
|
+
{ tgAttraction :: Attraction
|
|
199
|
+
, tgSubjects :: [String]
|
|
200
|
+
, tgSearchReport :: SearchReport
|
|
201
|
+
} deriving (Show, Eq)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```plantuml
|
|
205
|
+
@startuml
|
|
206
|
+
!theme plain
|
|
207
|
+
|
|
208
|
+
class TravelGuide {
|
|
209
|
+
tgAttraction: Attraction
|
|
210
|
+
tgSubjects: [String]
|
|
211
|
+
tgSearchReport: SearchReport
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class SearchReport {
|
|
215
|
+
srAttractionsSearched: Int
|
|
216
|
+
srErrors: [String]
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
TravelGuide --> SearchReport
|
|
220
|
+
|
|
221
|
+
note right of SearchReport
|
|
222
|
+
検索の統計情報と
|
|
223
|
+
エラー情報を保持
|
|
224
|
+
end note
|
|
225
|
+
|
|
226
|
+
@enduml
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 12.6 アプリケーションロジック
|
|
230
|
+
|
|
231
|
+
```haskell
|
|
232
|
+
-- 旅行ガイドを取得(SearchReport 付き)
|
|
233
|
+
travelGuideWithReport :: DataAccess -> String -> IO (Maybe TravelGuide)
|
|
234
|
+
travelGuideWithReport da attractionName = do
|
|
235
|
+
attractions <- findAttractions da attractionName AttrByLocationPopulation 3
|
|
236
|
+
case attractions of
|
|
237
|
+
[] -> return Nothing
|
|
238
|
+
(attraction:_) -> do
|
|
239
|
+
let locId' = locId $ attrLocation attraction
|
|
240
|
+
artistsResult <- findArtistsFromLocation da locId' 2
|
|
241
|
+
moviesResult <- findMoviesAboutLocation da locId' 2
|
|
242
|
+
|
|
243
|
+
let errors = collectErrors [artistsResult, moviesResult]
|
|
244
|
+
let artists = either (const []) id artistsResult
|
|
245
|
+
let movies = either (const []) id moviesResult
|
|
246
|
+
let subjects = map artistName artists ++ map movieName movies
|
|
247
|
+
|
|
248
|
+
return $ Just TravelGuide
|
|
249
|
+
{ tgAttraction = attraction
|
|
250
|
+
, tgSubjects = subjects
|
|
251
|
+
, tgSearchReport = SearchReport (length attractions) errors
|
|
252
|
+
}
|
|
253
|
+
where
|
|
254
|
+
collectErrors :: [Either String a] -> [String]
|
|
255
|
+
collectErrors = foldr (\r acc -> either (:acc) (const acc) r) []
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```plantuml
|
|
259
|
+
@startuml
|
|
260
|
+
!theme plain
|
|
261
|
+
|
|
262
|
+
rectangle "travelGuideWithReport 関数" {
|
|
263
|
+
card "1. アトラクション検索" as step1
|
|
264
|
+
card "2. アーティスト検索" as step2
|
|
265
|
+
card "3. 映画検索" as step3
|
|
266
|
+
card "4. エラー収集" as step4
|
|
267
|
+
card "5. TravelGuide 組み立て" as step5
|
|
268
|
+
|
|
269
|
+
step1 --> step2 : attraction
|
|
270
|
+
step1 --> step3 : location.id
|
|
271
|
+
step2 --> step4 : Either
|
|
272
|
+
step3 --> step4 : Either
|
|
273
|
+
step4 --> step5 : errors
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
note bottom
|
|
277
|
+
do 記法で
|
|
278
|
+
複数の IO を合成
|
|
279
|
+
end note
|
|
280
|
+
|
|
281
|
+
@enduml
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 12.7 キャッシュの実装
|
|
285
|
+
|
|
286
|
+
`IORef` を使用したスレッドセーフなキャッシュの実装:
|
|
287
|
+
|
|
288
|
+
```haskell
|
|
289
|
+
import Data.IORef
|
|
290
|
+
import Data.Map.Strict (Map)
|
|
291
|
+
import qualified Data.Map.Strict as Map
|
|
292
|
+
|
|
293
|
+
-- キャッシュ付きデータアクセス
|
|
294
|
+
type CachedDataAccess = (DataAccess, IORef (Map String [Attraction]))
|
|
295
|
+
|
|
296
|
+
-- キャッシュ付きデータアクセスの作成
|
|
297
|
+
mkCachedDataAccess :: DataAccess -> IO CachedDataAccess
|
|
298
|
+
mkCachedDataAccess da = do
|
|
299
|
+
cache <- newIORef Map.empty
|
|
300
|
+
return (da, cache)
|
|
301
|
+
|
|
302
|
+
-- キャッシュ付きアトラクション検索
|
|
303
|
+
cachedFindAttractions :: CachedDataAccess -> String -> AttractionOrdering -> Int -> IO [Attraction]
|
|
304
|
+
cachedFindAttractions (da, cache) name ordering limit = do
|
|
305
|
+
let key = name ++ "-" ++ show ordering ++ "-" ++ show limit
|
|
306
|
+
cached <- Map.lookup key <$> readIORef cache
|
|
307
|
+
case cached of
|
|
308
|
+
Just attractions -> return attractions -- キャッシュヒット
|
|
309
|
+
Nothing -> do
|
|
310
|
+
attractions <- findAttractions da name ordering limit
|
|
311
|
+
atomicModifyIORef' cache (\m -> (Map.insert key attractions m, ()))
|
|
312
|
+
return attractions
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```plantuml
|
|
316
|
+
@startuml
|
|
317
|
+
!theme plain
|
|
318
|
+
skinparam activity {
|
|
319
|
+
BackgroundColor White
|
|
320
|
+
BorderColor Black
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
start
|
|
324
|
+
|
|
325
|
+
:キャッシュキー生成;
|
|
326
|
+
|
|
327
|
+
:readIORef でキャッシュ確認;
|
|
328
|
+
|
|
329
|
+
if (キャッシュにデータあり?) then (yes)
|
|
330
|
+
:キャッシュからデータ返却;
|
|
331
|
+
else (no)
|
|
332
|
+
:外部 API を呼び出し;
|
|
333
|
+
:atomicModifyIORef' でキャッシュ保存;
|
|
334
|
+
:結果を返却;
|
|
335
|
+
endif
|
|
336
|
+
|
|
337
|
+
stop
|
|
338
|
+
|
|
339
|
+
note right
|
|
340
|
+
IORef を使用した
|
|
341
|
+
スレッドセーフなキャッシュ
|
|
342
|
+
end note
|
|
343
|
+
|
|
344
|
+
@enduml
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 12.8 リソース管理(bracket)
|
|
348
|
+
|
|
349
|
+
Haskell の `bracket` を使用して、安全なリソース管理を実現します。
|
|
350
|
+
|
|
351
|
+
```haskell
|
|
352
|
+
import Control.Exception (bracket)
|
|
353
|
+
import System.IO (Handle, openFile, hClose, hGetContents, IOMode(..))
|
|
354
|
+
|
|
355
|
+
-- リソースを安全に使用
|
|
356
|
+
withResource :: IO a -> (a -> IO ()) -> (a -> IO b) -> IO b
|
|
357
|
+
withResource acquire release = bracket acquire release
|
|
358
|
+
|
|
359
|
+
-- ファイルを安全に開く
|
|
360
|
+
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
|
|
361
|
+
withFile' path mode = bracket (openFile path mode) hClose
|
|
362
|
+
|
|
363
|
+
-- 使用例
|
|
364
|
+
readFileContents :: FilePath -> IO (Either String String)
|
|
365
|
+
readFileContents path = do
|
|
366
|
+
result <- try $ withFile' path ReadMode hGetContents
|
|
367
|
+
case result of
|
|
368
|
+
Left e -> return $ Left (show e)
|
|
369
|
+
Right c -> return $ Right c
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
```plantuml
|
|
373
|
+
@startuml
|
|
374
|
+
!theme plain
|
|
375
|
+
|
|
376
|
+
participant "Application" as app
|
|
377
|
+
participant "bracket" as res
|
|
378
|
+
participant "Handle" as handle
|
|
379
|
+
participant "File" as file
|
|
380
|
+
|
|
381
|
+
app -> res: bracket acquire release use
|
|
382
|
+
res -> handle: acquire: openFile
|
|
383
|
+
handle -> file: open
|
|
384
|
+
file --> handle: handle
|
|
385
|
+
|
|
386
|
+
app -> res: use (read contents)
|
|
387
|
+
res -> handle: hGetContents
|
|
388
|
+
handle -> file: read
|
|
389
|
+
file --> handle: contents
|
|
390
|
+
handle --> res: contents
|
|
391
|
+
res --> app: contents
|
|
392
|
+
|
|
393
|
+
app -> res: release (automatic)
|
|
394
|
+
res -> handle: hClose
|
|
395
|
+
handle -> file: close
|
|
396
|
+
|
|
397
|
+
note over res
|
|
398
|
+
bracket は例外が発生しても
|
|
399
|
+
必ず release を実行する
|
|
400
|
+
end note
|
|
401
|
+
|
|
402
|
+
@enduml
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Scala との対応:
|
|
406
|
+
|
|
407
|
+
- `Resource[IO, A]` → `bracket acquire release use`
|
|
408
|
+
- `Resource.make(acquire)(release)` → `bracket acquire release`
|
|
409
|
+
|
|
410
|
+
### 12.9 純粋関数のテスト
|
|
411
|
+
|
|
412
|
+
純粋関数は副作用がないため、テストが非常に簡単です。
|
|
413
|
+
|
|
414
|
+
```haskell
|
|
415
|
+
-- 人口でロケーションをフィルタリング(純粋関数)
|
|
416
|
+
filterPopularLocations :: [Location] -> Int -> [Location]
|
|
417
|
+
filterPopularLocations locations minPopulation =
|
|
418
|
+
filter (\loc -> locPopulation loc >= minPopulation) locations
|
|
419
|
+
|
|
420
|
+
-- アトラクションを人口でソート(純粋関数)
|
|
421
|
+
sortAttractionsByPopulation :: [Attraction] -> [Attraction]
|
|
422
|
+
sortAttractionsByPopulation =
|
|
423
|
+
sortBy (\a b -> compare (Down $ locPopulation $ attrLocation a)
|
|
424
|
+
(Down $ locPopulation $ attrLocation b))
|
|
425
|
+
|
|
426
|
+
-- バリデーション(純粋関数)
|
|
427
|
+
validateLocation :: Location -> Either String Location
|
|
428
|
+
validateLocation loc
|
|
429
|
+
| null (unLocationId $ locId loc) = Left "Location ID cannot be empty"
|
|
430
|
+
| null (locName loc) = Left "Location name cannot be empty"
|
|
431
|
+
| locPopulation loc < 0 = Left "Population cannot be negative"
|
|
432
|
+
| otherwise = Right loc
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### 12.10 HSpec によるユニットテスト
|
|
436
|
+
|
|
437
|
+
```haskell
|
|
438
|
+
import Test.Hspec
|
|
439
|
+
|
|
440
|
+
spec :: Spec
|
|
441
|
+
spec = do
|
|
442
|
+
describe "filterPopularLocations" $ do
|
|
443
|
+
it "filters by minimum population" $ do
|
|
444
|
+
let locs = [ Location (LocationId "1") "A" 100
|
|
445
|
+
, Location (LocationId "2") "B" 200
|
|
446
|
+
, Location (LocationId "3") "C" 150
|
|
447
|
+
]
|
|
448
|
+
let filtered = filterPopularLocations locs 150
|
|
449
|
+
length filtered `shouldBe` 2
|
|
450
|
+
|
|
451
|
+
it "returns empty for high minimum" $ do
|
|
452
|
+
let locs = [Location (LocationId "1") "A" 100]
|
|
453
|
+
filterPopularLocations locs 1000 `shouldBe` []
|
|
454
|
+
|
|
455
|
+
describe "validateLocation" $ do
|
|
456
|
+
it "validates correct location" $ do
|
|
457
|
+
let loc = Location (LocationId "Q1") "Tokyo" 14000000
|
|
458
|
+
validateLocation loc `shouldBe` Right loc
|
|
459
|
+
|
|
460
|
+
it "rejects empty ID" $ do
|
|
461
|
+
let loc = Location (LocationId "") "Tokyo" 14000000
|
|
462
|
+
validateLocation loc `shouldBe` Left "Location ID cannot be empty"
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 12.11 QuickCheck によるプロパティベーステスト
|
|
466
|
+
|
|
467
|
+
QuickCheck を使用したプロパティベーステスト:
|
|
468
|
+
|
|
469
|
+
```haskell
|
|
470
|
+
import Test.QuickCheck
|
|
471
|
+
|
|
472
|
+
-- Arbitrary インスタンスの定義
|
|
473
|
+
instance Arbitrary LocationId where
|
|
474
|
+
arbitrary = LocationId <$> listOf1 (elements ['a'..'z'])
|
|
475
|
+
|
|
476
|
+
instance Arbitrary Location where
|
|
477
|
+
arbitrary = Location
|
|
478
|
+
<$> arbitrary
|
|
479
|
+
<*> listOf1 (elements ['A'..'Z'])
|
|
480
|
+
<*> (abs <$> arbitrary)
|
|
481
|
+
|
|
482
|
+
-- プロパティテスト
|
|
483
|
+
spec :: Spec
|
|
484
|
+
spec = do
|
|
485
|
+
describe "Property-based tests" $ do
|
|
486
|
+
it "filterPopularLocations result size <= input size" $
|
|
487
|
+
property $ \(locs :: [Location]) (minPop :: Int) ->
|
|
488
|
+
length (filterPopularLocations locs (abs minPop)) <= length locs
|
|
489
|
+
|
|
490
|
+
it "filterPopularLocations all results meet minimum" $
|
|
491
|
+
property $ \(locs :: [Location]) (minPop :: Int) ->
|
|
492
|
+
let filtered = filterPopularLocations locs (abs minPop)
|
|
493
|
+
in all (\loc -> locPopulation loc >= abs minPop) filtered
|
|
494
|
+
|
|
495
|
+
it "sortAttractionsByPopulation preserves length" $
|
|
496
|
+
property $ \(attrs :: [Attraction]) ->
|
|
497
|
+
length (sortAttractionsByPopulation attrs) == length attrs
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
```plantuml
|
|
501
|
+
@startuml
|
|
502
|
+
!theme plain
|
|
503
|
+
|
|
504
|
+
rectangle "プロパティベーステスト" {
|
|
505
|
+
card "Arbitrary インスタンス" as gen
|
|
506
|
+
card "ランダムデータ生成" as random
|
|
507
|
+
card "プロパティ検証" as verify
|
|
508
|
+
card "100回以上テスト" as repeat
|
|
509
|
+
|
|
510
|
+
gen --> random
|
|
511
|
+
random --> verify
|
|
512
|
+
verify --> repeat
|
|
513
|
+
repeat --> random : 繰り返し
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
note bottom
|
|
517
|
+
QuickCheck が自動で
|
|
518
|
+
ランダムな入力を生成
|
|
519
|
+
end note
|
|
520
|
+
|
|
521
|
+
@enduml
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Scala との対応:
|
|
525
|
+
|
|
526
|
+
- ScalaCheck `Gen[A]` → QuickCheck `Arbitrary a`
|
|
527
|
+
- `forAll` → `property`
|
|
528
|
+
|
|
529
|
+
### 12.12 統合テスト
|
|
530
|
+
|
|
531
|
+
```haskell
|
|
532
|
+
describe "Integration tests" $ do
|
|
533
|
+
it "travelGuide returns guide for valid attraction" $ do
|
|
534
|
+
da <- mkTestDataAccess
|
|
535
|
+
guide <- travelGuide da "Test"
|
|
536
|
+
guide `shouldSatisfy` \g -> case g of
|
|
537
|
+
Just g' -> attrName (tgAttraction g') == "Test Attraction"
|
|
538
|
+
Nothing -> False
|
|
539
|
+
|
|
540
|
+
it "travelGuideWithReport collects errors" $ do
|
|
541
|
+
da <- mkFailingDataAccess
|
|
542
|
+
guide <- travelGuideWithReport da "Test"
|
|
543
|
+
case guide of
|
|
544
|
+
Just g -> do
|
|
545
|
+
let errors = srErrors (tgSearchReport g)
|
|
546
|
+
length errors `shouldBe` 2
|
|
547
|
+
errors `shouldContain` ["Network error"]
|
|
548
|
+
Nothing -> expectationFailure "Expected Just"
|
|
549
|
+
|
|
550
|
+
it "still returns guide even with errors" $ do
|
|
551
|
+
da <- mkFailingDataAccess
|
|
552
|
+
guide <- travelGuideWithReport da "Test"
|
|
553
|
+
guide `shouldSatisfy` \g -> case g of
|
|
554
|
+
Just _ -> True
|
|
555
|
+
Nothing -> False
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### 12.13 テストピラミッド
|
|
559
|
+
|
|
560
|
+
```plantuml
|
|
561
|
+
@startuml
|
|
562
|
+
!theme plain
|
|
563
|
+
|
|
564
|
+
rectangle "テストピラミッド" {
|
|
565
|
+
rectangle "E2E テスト\n(少数)" as e2e
|
|
566
|
+
rectangle "統合テスト\n(中程度)" as integration
|
|
567
|
+
rectangle "単体テスト + プロパティテスト\n(多数)" as unit
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
e2e -[hidden]down- integration
|
|
571
|
+
integration -[hidden]down- unit
|
|
572
|
+
|
|
573
|
+
note right of unit
|
|
574
|
+
Haskell では純粋関数が多いため
|
|
575
|
+
単体テストとプロパティテストが
|
|
576
|
+
非常に効果的
|
|
577
|
+
end note
|
|
578
|
+
|
|
579
|
+
@enduml
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## まとめ
|
|
585
|
+
|
|
586
|
+
### Part VI で学んだこと
|
|
587
|
+
|
|
588
|
+
```plantuml
|
|
589
|
+
@startuml
|
|
590
|
+
!theme plain
|
|
591
|
+
|
|
592
|
+
rectangle "Part VI: 実践的なアプリケーション" {
|
|
593
|
+
rectangle "第12章" as ch12 {
|
|
594
|
+
card "ドメインモデル設計"
|
|
595
|
+
card "DataAccess 抽象化"
|
|
596
|
+
card "bracket リソース管理"
|
|
597
|
+
card "IORef キャッシュ"
|
|
598
|
+
card "SearchReport"
|
|
599
|
+
card "HSpec ユニットテスト"
|
|
600
|
+
card "QuickCheck プロパティテスト"
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
@enduml
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Scala と Haskell の比較
|
|
608
|
+
|
|
609
|
+
| 概念 | Scala | Haskell |
|
|
610
|
+
|------|-------|---------|
|
|
611
|
+
| インターフェース | `trait` | `data` (レコード型) |
|
|
612
|
+
| 値オブジェクト | `opaque type` | `newtype` |
|
|
613
|
+
| オプショナル | `Option[A]` | `Maybe a` |
|
|
614
|
+
| リソース管理 | `Resource[IO, A]` | `bracket` |
|
|
615
|
+
| キャッシュ | `Ref[IO, Map]` | `IORef (Map ...)` |
|
|
616
|
+
| ユニットテスト | ScalaTest | HSpec |
|
|
617
|
+
| プロパティテスト | ScalaCheck | QuickCheck |
|
|
618
|
+
| ジェネレータ | `Gen[A]` | `Arbitrary a` |
|
|
619
|
+
|
|
620
|
+
### キーポイント
|
|
621
|
+
|
|
622
|
+
1. **抽象化の重要性**: レコード型で外部依存を抽象化
|
|
623
|
+
2. **bracket でリソース管理**: 安全なリソースの取得と解放
|
|
624
|
+
3. **IORef でキャッシュ**: スレッドセーフな状態管理
|
|
625
|
+
4. **Either でエラー処理**: 明示的なエラーハンドリング
|
|
626
|
+
5. **SearchReport**: テスト可能性と可観測性の向上
|
|
627
|
+
6. **スタブ**: 外部依存を差し替えてテスト
|
|
628
|
+
7. **プロパティベーステスト**: QuickCheck でランダム入力による不変条件の検証
|
|
629
|
+
|
|
630
|
+
### 学習の総括
|
|
631
|
+
|
|
632
|
+
```plantuml
|
|
633
|
+
@startuml
|
|
634
|
+
!theme plain
|
|
635
|
+
left to right direction
|
|
636
|
+
|
|
637
|
+
rectangle "FP の学習パス" {
|
|
638
|
+
card "Part I\n基礎" as p1
|
|
639
|
+
card "Part II\nイミュータブル操作" as p2
|
|
640
|
+
card "Part III\n型による安全性" as p3
|
|
641
|
+
card "Part IV\nIO/Stream" as p4
|
|
642
|
+
card "Part V\n並行処理" as p5
|
|
643
|
+
card "Part VI\n実践" as p6
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
p1 --> p2
|
|
647
|
+
p2 --> p3
|
|
648
|
+
p3 --> p4
|
|
649
|
+
p4 --> p5
|
|
650
|
+
p5 --> p6
|
|
651
|
+
|
|
652
|
+
@enduml
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## 演習問題
|
|
658
|
+
|
|
659
|
+
### 問題 1: DataAccess の拡張
|
|
660
|
+
|
|
661
|
+
以下の要件で `DataAccess` を拡張してください:
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
- 新しいフィールド `findHotelsNearLocation` を追加
|
|
665
|
+
- 戻り値は `IO (Either String [Hotel])`
|
|
666
|
+
|
|
667
|
+
```haskell
|
|
668
|
+
data Hotel = Hotel
|
|
669
|
+
{ hotelName :: String
|
|
670
|
+
, hotelRating :: Double
|
|
671
|
+
, hotelLocation :: Location
|
|
672
|
+
} deriving (Show, Eq)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
<details>
|
|
676
|
+
<summary>解答</summary>
|
|
677
|
+
|
|
678
|
+
```haskell
|
|
679
|
+
data DataAccess = DataAccess
|
|
680
|
+
{ findAttractions :: String -> AttractionOrdering -> Int -> IO [Attraction]
|
|
681
|
+
, findArtistsFromLocation :: LocationId -> Int -> IO (Either String [MusicArtist])
|
|
682
|
+
, findMoviesAboutLocation :: LocationId -> Int -> IO (Either String [Movie])
|
|
683
|
+
, findHotelsNearLocation :: LocationId -> Int -> IO (Either String [Hotel])
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
-- テスト用スタブ
|
|
687
|
+
mkTestDataAccess :: IO DataAccess
|
|
688
|
+
mkTestDataAccess = return DataAccess
|
|
689
|
+
{ -- 既存の実装...
|
|
690
|
+
, findHotelsNearLocation = \_ limit ->
|
|
691
|
+
return $ Right $ take limit
|
|
692
|
+
[Hotel "Test Hotel" 4.5 (Location (LocationId "Q123") "Test City" 100000)]
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
</details>
|
|
697
|
+
|
|
698
|
+
### 問題 2: プロパティベーステスト
|
|
699
|
+
|
|
700
|
+
以下の関数に対するプロパティベーステストを書いてください:
|
|
701
|
+
|
|
702
|
+
```haskell
|
|
703
|
+
combineSubjects :: [MusicArtist] -> [Movie] -> [String]
|
|
704
|
+
combineSubjects artists movies =
|
|
705
|
+
map artistName artists ++ map movieName movies
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
<details>
|
|
709
|
+
<summary>解答</summary>
|
|
710
|
+
|
|
711
|
+
```haskell
|
|
712
|
+
import Test.QuickCheck
|
|
713
|
+
|
|
714
|
+
instance Arbitrary MusicArtist where
|
|
715
|
+
arbitrary = MusicArtist <$> listOf1 (elements ['A'..'Z'])
|
|
716
|
+
|
|
717
|
+
instance Arbitrary Movie where
|
|
718
|
+
arbitrary = Movie <$> listOf1 (elements ['A'..'Z'])
|
|
719
|
+
|
|
720
|
+
spec :: Spec
|
|
721
|
+
spec = do
|
|
722
|
+
describe "combineSubjects properties" $ do
|
|
723
|
+
it "length is sum of inputs" $
|
|
724
|
+
property $ \(artists :: [MusicArtist]) (movies :: [Movie]) ->
|
|
725
|
+
length (combineSubjects artists movies) == length artists + length movies
|
|
726
|
+
|
|
727
|
+
it "artists come first" $
|
|
728
|
+
property $ \(artists :: [MusicArtist]) (movies :: [Movie]) ->
|
|
729
|
+
let result = combineSubjects artists movies
|
|
730
|
+
artistNames = map artistName artists
|
|
731
|
+
in take (length artists) result == artistNames
|
|
732
|
+
|
|
733
|
+
it "movies come after artists" $
|
|
734
|
+
property $ \(artists :: [MusicArtist]) (movies :: [Movie]) ->
|
|
735
|
+
let result = combineSubjects artists movies
|
|
736
|
+
movieNames = map movieName movies
|
|
737
|
+
in drop (length artists) result == movieNames
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
</details>
|
|
741
|
+
|
|
742
|
+
### 問題 3: bracket の実装
|
|
743
|
+
|
|
744
|
+
データベース接続を安全に管理する関数を実装してください。
|
|
745
|
+
|
|
746
|
+
```haskell
|
|
747
|
+
data Connection = Connection { connId :: Int }
|
|
748
|
+
|
|
749
|
+
-- 実装してください
|
|
750
|
+
withConnection :: IO Connection -> (Connection -> IO a) -> IO a
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
<details>
|
|
754
|
+
<summary>解答</summary>
|
|
755
|
+
|
|
756
|
+
```haskell
|
|
757
|
+
import Control.Exception (bracket)
|
|
758
|
+
|
|
759
|
+
data Connection = Connection { connId :: Int }
|
|
760
|
+
|
|
761
|
+
-- 接続を開く
|
|
762
|
+
openConnection :: IO Connection
|
|
763
|
+
openConnection = do
|
|
764
|
+
putStrLn "Opening connection..."
|
|
765
|
+
return $ Connection 1
|
|
766
|
+
|
|
767
|
+
-- 接続を閉じる
|
|
768
|
+
closeConnection :: Connection -> IO ()
|
|
769
|
+
closeConnection conn = putStrLn $ "Closing connection " ++ show (connId conn)
|
|
770
|
+
|
|
771
|
+
-- 安全に接続を使用
|
|
772
|
+
withConnection :: (Connection -> IO a) -> IO a
|
|
773
|
+
withConnection = bracket openConnection closeConnection
|
|
774
|
+
|
|
775
|
+
-- 使用例
|
|
776
|
+
queryDatabase :: IO String
|
|
777
|
+
queryDatabase = withConnection $ \conn -> do
|
|
778
|
+
putStrLn $ "Querying with connection " ++ show (connId conn)
|
|
779
|
+
return "Query result"
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
</details>
|
|
783
|
+
|
|
784
|
+
### 問題 4: エラー収集
|
|
785
|
+
|
|
786
|
+
複数の `Either` からエラーを収集する関数を実装してください。
|
|
787
|
+
|
|
788
|
+
```haskell
|
|
789
|
+
collectAllErrors :: [Either String a] -> [String]
|
|
790
|
+
collectAllErrors = ???
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
<details>
|
|
794
|
+
<summary>解答</summary>
|
|
795
|
+
|
|
796
|
+
```haskell
|
|
797
|
+
collectAllErrors :: [Either String a] -> [String]
|
|
798
|
+
collectAllErrors = foldr collect []
|
|
799
|
+
where
|
|
800
|
+
collect (Left err) acc = err : acc
|
|
801
|
+
collect (Right _) acc = acc
|
|
802
|
+
|
|
803
|
+
-- または
|
|
804
|
+
collectAllErrors :: [Either String a] -> [String]
|
|
805
|
+
collectAllErrors results = [err | Left err <- results]
|
|
806
|
+
|
|
807
|
+
-- テスト
|
|
808
|
+
-- collectAllErrors [Right 1, Left "Error1", Right 2, Left "Error2"]
|
|
809
|
+
-- => ["Error1", "Error2"]
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
</details>
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## シリーズ全体の総括
|
|
817
|
+
|
|
818
|
+
本シリーズでは、「Grokking Functional Programming」の内容に沿って、Haskell で関数型プログラミングの基礎から実践的なアプリケーション構築までを学びました。
|
|
819
|
+
|
|
820
|
+
### 学んだ主な概念
|
|
821
|
+
|
|
822
|
+
| Part | 章 | 主な概念 |
|
|
823
|
+
|------|-----|----------|
|
|
824
|
+
| I | 1-2 | 純粋関数、参照透過性、型推論 |
|
|
825
|
+
| II | 3-5 | イミュータブルデータ、高階関数、do 記法 |
|
|
826
|
+
| III | 6-7 | Maybe、Either、パターンマッチング |
|
|
827
|
+
| IV | 8-9 | IO モナド、遅延リスト(無限ストリーム) |
|
|
828
|
+
| V | 10-11 | 並行処理、IORef、Async、STM |
|
|
829
|
+
| VI | 12 | 実践アプリケーション、テスト戦略 |
|
|
830
|
+
|
|
831
|
+
### Haskell の関数型プログラミングの利点
|
|
832
|
+
|
|
833
|
+
1. **純粋性の保証**: 型システムが純粋関数と IO を分離
|
|
834
|
+
2. **遅延評価**: 無限リストが自然に扱える
|
|
835
|
+
3. **強力な型システム**: newtype、ADT で安全なモデリング
|
|
836
|
+
4. **STM**: デッドロックフリーな並行処理
|
|
837
|
+
5. **QuickCheck**: プロパティベーステストの先駆者
|
|
838
|
+
|
|
839
|
+
### 次のステップ
|
|
840
|
+
|
|
841
|
+
- Monad Transformers(モナドトランスフォーマー)を学ぶ
|
|
842
|
+
- lens ライブラリでデータ操作を効率化
|
|
843
|
+
- servant で型安全な Web API を構築
|
|
844
|
+
- 実際のプロジェクトで Haskell を適用する
|