@k2works/claude-code-booster 3.0.0 → 3.1.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/lib/assets/docs/article/functional-desgin-ppp/all/01-immutability-and-data-transformation.md +475 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/02-function-composition.md +519 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/03-polymorphism.md +537 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/04-data-validation.md +300 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/05-property-based-testing.md +320 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/06-tdd-and-functional.md +498 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/07-composite-pattern.md +298 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/08-decorator-pattern.md +291 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/09-adapter-pattern.md +336 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/10-strategy-pattern.md +303 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/11-command-pattern.md +286 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/12-visitor-pattern.md +322 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/13-abstract-factory-pattern.md +319 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/14-abstract-server-pattern.md +365 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/15-gossiping-bus-drivers.md +156 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/16-payroll-system.md +178 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/17-video-rental-system.md +312 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/18-concurrency-system.md +287 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/19-wa-tor-simulation.md +286 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/20-pattern-interactions.md +274 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/21-best-practices.md +294 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/22-oo-to-fp-migration.md +337 -0
- package/lib/assets/docs/article/functional-desgin-ppp/all/index.md +388 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/01-immutability-and-data-transformation.md +271 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/02-function-composition.md +380 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/03-polymorphism.md +384 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/04-clojure-spec.md +350 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/05-property-based-testing.md +352 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/06-tdd-in-functional.md +383 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/07-composite-pattern.md +529 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/08-decorator-pattern.md +395 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/09-adapter-pattern.md +399 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/10-strategy-pattern.md +485 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/11-command-pattern.md +566 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/12-visitor-pattern.md +567 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/13-abstract-factory-pattern.md +475 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/14-abstract-server-pattern.md +462 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/15-gossiping-bus-drivers.md +323 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/16-payroll-system.md +401 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/17-video-rental-system.md +450 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/18-concurrency-system.md +475 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/19-wator-simulation.md +739 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/20-pattern-interactions.md +562 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/21-best-practices.md +506 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/22-oo-to-fp-migration.md +526 -0
- package/lib/assets/docs/article/functional-desgin-ppp/clojure/index.md +197 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/01-immutability-and-data-transformation.md +381 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/02-function-composition.md +374 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/03-polymorphism.md +375 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/04-data-validation.md +195 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/05-property-based-testing.md +268 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/06-tdd-and-fp.md +294 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/07-effects-and-pure-functions.md +164 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/08-error-handling-strategies.md +168 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/09-io-and-external-systems.md +254 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/10-concurrency-patterns.md +269 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/11-command-pattern.md +148 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/12-visitor-pattern.md +176 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/13-abstract-factory-pattern.md +604 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/14-abstract-server-pattern.md +729 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/15-gossiping-bus-drivers.md +291 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/16-payroll-system.md +420 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/17-video-rental-system.md +319 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/18-concurrency-system.md +466 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/19-wator-simulation.md +523 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/20-pattern-interactions.md +287 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/21-best-practices.md +340 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/22-oo-to-fp-migration.md +395 -0
- package/lib/assets/docs/article/functional-desgin-ppp/elixir/index.md +204 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/01-immutability-and-data-transformation.md +382 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/02-function-composition.md +452 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/03-polymorphism.md +495 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/04-data-validation.md +416 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/05-property-based-testing.md +382 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/06-tdd-functional.md +687 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/07-composite-pattern.md +442 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/08-decorator-pattern.md +479 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/09-adapter-pattern.md +479 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/10-strategy-pattern.md +427 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/11-command-pattern.md +428 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/12-visitor-pattern.md +339 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/13-abstract-factory-pattern.md +309 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/14-abstract-server-pattern.md +596 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/15-gossiping-bus-drivers.md +353 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/16-payroll-system.md +350 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/17-video-rental-system.md +412 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/18-concurrency-system.md +367 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/19-wator-simulation.md +401 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/20-pattern-interactions.md +291 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/21-best-practices.md +320 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/22-oo-to-fp-migration.md +322 -0
- package/lib/assets/docs/article/functional-desgin-ppp/fsharp/index.md +230 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/01-immutability-and-data-transformation.md +298 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/02-function-composition.md +304 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/03-polymorphism.md +362 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/04-data-validation.md +257 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/05-property-based-testing.md +254 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/06-tdd-functional.md +283 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/07-composite-pattern.md +395 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/08-decorator-pattern.md +319 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/09-adapter-pattern.md +382 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/10-strategy-pattern.md +287 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/11-command-pattern.md +303 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/12-visitor-pattern.md +326 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/13-abstract-factory-pattern.md +332 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/14-abstract-server-pattern.md +379 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/15-gossiping-bus-drivers.md +175 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/16-payroll-system.md +219 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/17-video-rental-system.md +244 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/18-concurrency-system.md +363 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/19-wator-simulation.md +438 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/20-pattern-interactions.md +323 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/21-best-practices.md +403 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/22-oo-to-fp-migration.md +469 -0
- package/lib/assets/docs/article/functional-desgin-ppp/haskell/index.md +174 -0
- package/lib/assets/docs/article/functional-desgin-ppp/index.md +90 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/01-immutability-and-data-transformation.md +448 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/02-function-composition.md +463 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/03-polymorphism.md +425 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/04-data-validation.md +273 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/05-property-based-testing.md +247 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/06-tdd-and-functional.md +841 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/07-composite-pattern.md +384 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/08-decorator-pattern.md +383 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/09-adapter-pattern.md +339 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/10-strategy-pattern.md +331 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/11-command-pattern.md +356 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/12-visitor-pattern.md +379 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/13-abstract-factory-pattern.md +361 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/14-abstract-server-pattern.md +392 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/15-gossiping-bus-drivers.md +300 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/16-payroll-system.md +297 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/17-video-rental-system.md +304 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/18-concurrency-system.md +315 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/19-wator-simulation.md +311 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/20-pattern-interactions.md +304 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/21-best-practices.md +336 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/22-oo-to-fp-migration.md +349 -0
- package/lib/assets/docs/article/functional-desgin-ppp/rust/index.md +199 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/01-immutability-and-data-transformation.md +326 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/02-function-composition.md +348 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/03-polymorphism.md +357 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/04-data-validation.md +364 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/05-property-based-testing.md +515 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/06-tdd-functional.md +557 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/07-composite-pattern.md +363 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/08-decorator-pattern.md +327 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/09-adapter-pattern.md +517 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/10-strategy-pattern.md +441 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/11-command-pattern.md +407 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/12-visitor-pattern.md +379 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/13-abstract-factory-pattern.md +398 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/14-abstract-server-pattern.md +476 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/15-gossiping-bus-drivers.md +389 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/16-payroll-system.md +342 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/17-video-rental-system.md +324 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/18-concurrency-system.md +730 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/19-wator-simulation.md +624 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/20-pattern-interactions.md +512 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/21-best-practices.md +427 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/22-oo-to-fp-migration.md +682 -0
- package/lib/assets/docs/article/functional-desgin-ppp/scala/index.md +199 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/01-todo-list-and-first-test.md +166 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/02-fake-it-and-triangulation.md +162 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/03-obvious-implementation-and-refactoring.md +135 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/04-version-control-and-conventional-commits.md +88 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/05-package-management-and-static-analysis.md +299 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/06-task-runner-and-ci-cd.md +241 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/07-protocols-and-records.md +131 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/08-multimethods-and-design-patterns.md +130 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/09-namespaces-and-module-design.md +127 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/10-higher-order-functions-and-composition.md +114 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/11-persistent-data-and-pipeline.md +138 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/12-error-handling-and-spec.md +161 -0
- package/lib/assets/docs/article/getting-start-tdd/clojure/index.md +65 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter01.md +232 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter02.md +244 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter03.md +202 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter04.md +92 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter05.md +256 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter06.md +195 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter07.md +214 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter08.md +249 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter09.md +174 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter10.md +166 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter11.md +192 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/chapter12.md +211 -0
- package/lib/assets/docs/article/getting-start-tdd/csharp/index.md +83 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/01-todo-list-and-first-test.md +87 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/02-fake-it-and-triangulation.md +95 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/03-obvious-implementation-and-refactoring.md +109 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/04-version-control-and-conventional-commits.md +96 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/05-package-management-and-static-analysis.md +88 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/06-task-runner-and-ci-cd.md +71 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/07-structs-and-protocols.md +110 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/08-pattern-matching-and-guards.md +108 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/09-module-design-and-behaviours.md +104 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/10-higher-order-functions-and-pipeline.md +178 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/11-stream-and-lazy-evaluation.md +142 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/12-error-handling-and-with.md +145 -0
- package/lib/assets/docs/article/getting-start-tdd/elixir/index.md +35 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter01.md +202 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter02.md +246 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter03.md +218 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter04.md +179 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter05.md +267 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter06.md +190 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter07.md +161 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter08.md +175 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter09.md +222 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter10.md +189 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter11.md +212 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter12.md +215 -0
- package/lib/assets/docs/article/getting-start-tdd/fsharp/index.md +71 -0
- package/lib/assets/docs/article/getting-start-tdd/go/01-todo-list-and-first-test.md +213 -0
- package/lib/assets/docs/article/getting-start-tdd/go/02-fake-it-and-triangulation.md +302 -0
- package/lib/assets/docs/article/getting-start-tdd/go/03-obvious-implementation-and-refactoring.md +339 -0
- package/lib/assets/docs/article/getting-start-tdd/go/04-version-control-and-conventional-commits.md +112 -0
- package/lib/assets/docs/article/getting-start-tdd/go/05-package-management-and-static-analysis.md +272 -0
- package/lib/assets/docs/article/getting-start-tdd/go/06-task-runner-and-ci-cd.md +233 -0
- package/lib/assets/docs/article/getting-start-tdd/go/07-encapsulation-and-polymorphism.md +394 -0
- package/lib/assets/docs/article/getting-start-tdd/go/08-design-patterns.md +422 -0
- package/lib/assets/docs/article/getting-start-tdd/go/09-solid-principles-and-module-design.md +400 -0
- package/lib/assets/docs/article/getting-start-tdd/go/10-higher-order-functions-and-composition.md +226 -0
- package/lib/assets/docs/article/getting-start-tdd/go/11-immutable-data-and-pipeline.md +296 -0
- package/lib/assets/docs/article/getting-start-tdd/go/12-error-handling-and-type-safety.md +411 -0
- package/lib/assets/docs/article/getting-start-tdd/go/index.md +83 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/01-todo-list-and-first-test.md +279 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/02-fake-it-and-triangulation.md +337 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/03-obvious-implementation-and-refactoring.md +257 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/04-version-control-and-conventional-commits.md +182 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/05-package-management-and-static-analysis.md +313 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/06-task-runner-and-ci-cd.md +309 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/07-algebraic-data-types-and-type-classes.md +412 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/08-pattern-matching-and-guards.md +390 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/09-module-design-and-smart-constructors.md +461 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/10-higher-order-functions-and-currying.md +434 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/11-function-composition-and-point-free.md +392 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/12-monad-and-error-handling.md +631 -0
- package/lib/assets/docs/article/getting-start-tdd/haskell/index.md +49 -0
- package/lib/assets/docs/article/getting-start-tdd/index.md +93 -0
- package/lib/assets/docs/article/getting-start-tdd/integration/01-language-overview.md +375 -0
- package/lib/assets/docs/article/getting-start-tdd/integration/02-test-framework-comparison.md +349 -0
- package/lib/assets/docs/article/getting-start-tdd/integration/03-tdd-pattern-comparison.md +445 -0
- package/lib/assets/docs/article/getting-start-tdd/integration/04-type-system-comparison.md +405 -0
- package/lib/assets/docs/article/getting-start-tdd/integration/05-dev-environment-comparison.md +330 -0
- package/lib/assets/docs/article/getting-start-tdd/integration/06-learning-roadmap.md +274 -0
- package/lib/assets/docs/article/getting-start-tdd/integration/index.md +69 -0
- package/lib/assets/docs/article/getting-start-tdd/java/01-todo-list-and-first-test.md +234 -0
- package/lib/assets/docs/article/getting-start-tdd/java/02-fake-it-and-triangulation.md +261 -0
- package/lib/assets/docs/article/getting-start-tdd/java/03-obvious-implementation-and-refactoring.md +185 -0
- package/lib/assets/docs/article/getting-start-tdd/java/04-version-control-and-conventional-commits.md +115 -0
- package/lib/assets/docs/article/getting-start-tdd/java/05-package-management-and-static-analysis.md +382 -0
- package/lib/assets/docs/article/getting-start-tdd/java/06-task-runner-and-ci-cd.md +272 -0
- package/lib/assets/docs/article/getting-start-tdd/java/07-encapsulation-and-polymorphism.md +626 -0
- package/lib/assets/docs/article/getting-start-tdd/java/08-design-patterns.md +393 -0
- package/lib/assets/docs/article/getting-start-tdd/java/09-solid-principles-and-module-design.md +310 -0
- package/lib/assets/docs/article/getting-start-tdd/java/10-higher-order-functions-and-composition.md +188 -0
- package/lib/assets/docs/article/getting-start-tdd/java/11-immutable-data-and-pipeline.md +167 -0
- package/lib/assets/docs/article/getting-start-tdd/java/12-error-handling-and-type-safety.md +205 -0
- package/lib/assets/docs/article/getting-start-tdd/java/index.md +61 -0
- package/lib/assets/docs/article/getting-start-tdd/node/01-todo-list-and-first-test.md +244 -0
- package/lib/assets/docs/article/getting-start-tdd/node/02-fake-it-and-triangulation.md +262 -0
- package/lib/assets/docs/article/getting-start-tdd/node/03-obvious-implementation-and-refactoring.md +169 -0
- package/lib/assets/docs/article/getting-start-tdd/node/04-version-control-and-conventional-commits.md +112 -0
- package/lib/assets/docs/article/getting-start-tdd/node/05-package-management-and-static-analysis.md +314 -0
- package/lib/assets/docs/article/getting-start-tdd/node/06-task-runner-and-ci-cd.md +235 -0
- package/lib/assets/docs/article/getting-start-tdd/node/07-encapsulation-and-polymorphism.md +327 -0
- package/lib/assets/docs/article/getting-start-tdd/node/08-design-patterns.md +322 -0
- package/lib/assets/docs/article/getting-start-tdd/node/09-solid-principles-and-module-design.md +285 -0
- package/lib/assets/docs/article/getting-start-tdd/node/10-higher-order-functions-and-composition.md +199 -0
- package/lib/assets/docs/article/getting-start-tdd/node/11-immutable-data-and-pipeline.md +207 -0
- package/lib/assets/docs/article/getting-start-tdd/node/12-error-handling-and-type-safety.md +295 -0
- package/lib/assets/docs/article/getting-start-tdd/node/index.md +56 -0
- package/lib/assets/docs/article/getting-start-tdd/php/01-todo-list-and-first-test.md +259 -0
- package/lib/assets/docs/article/getting-start-tdd/php/02-fake-it-and-triangulation.md +200 -0
- package/lib/assets/docs/article/getting-start-tdd/php/03-obvious-implementation-and-refactoring.md +248 -0
- package/lib/assets/docs/article/getting-start-tdd/php/04-version-control-and-conventional-commits.md +141 -0
- package/lib/assets/docs/article/getting-start-tdd/php/05-package-management-and-static-analysis.md +410 -0
- package/lib/assets/docs/article/getting-start-tdd/php/06-task-runner-and-ci-cd.md +321 -0
- package/lib/assets/docs/article/getting-start-tdd/php/07-encapsulation-and-polymorphism.md +372 -0
- package/lib/assets/docs/article/getting-start-tdd/php/08-design-patterns.md +453 -0
- package/lib/assets/docs/article/getting-start-tdd/php/09-solid-principles-and-module-design.md +460 -0
- package/lib/assets/docs/article/getting-start-tdd/php/10-higher-order-functions-and-composition.md +182 -0
- package/lib/assets/docs/article/getting-start-tdd/php/11-immutable-data-and-pipeline.md +266 -0
- package/lib/assets/docs/article/getting-start-tdd/php/12-error-handling-and-type-safety.md +308 -0
- package/lib/assets/docs/article/getting-start-tdd/php/index.md +84 -0
- package/lib/assets/docs/article/getting-start-tdd/python/01-todo-list-and-first-test.md +201 -0
- package/lib/assets/docs/article/getting-start-tdd/python/02-fake-it-and-triangulation.md +247 -0
- package/lib/assets/docs/article/getting-start-tdd/python/03-obvious-implementation-and-refactoring.md +199 -0
- package/lib/assets/docs/article/getting-start-tdd/python/04-version-control-and-conventional-commits.md +87 -0
- package/lib/assets/docs/article/getting-start-tdd/python/05-package-management-and-static-analysis.md +274 -0
- package/lib/assets/docs/article/getting-start-tdd/python/06-task-runner-and-ci-cd.md +190 -0
- package/lib/assets/docs/article/getting-start-tdd/python/07-encapsulation-and-polymorphism.md +208 -0
- package/lib/assets/docs/article/getting-start-tdd/python/08-design-patterns.md +172 -0
- package/lib/assets/docs/article/getting-start-tdd/python/09-solid-principles-and-module-design.md +130 -0
- package/lib/assets/docs/article/getting-start-tdd/python/10-higher-order-functions-and-composition.md +122 -0
- package/lib/assets/docs/article/getting-start-tdd/python/11-immutable-data-and-pipeline.md +116 -0
- package/lib/assets/docs/article/getting-start-tdd/python/12-error-handling-and-type-safety.md +126 -0
- package/lib/assets/docs/article/getting-start-tdd/python/index.md +55 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/01-todo-list-and-first-test.md +231 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/02-fake-it-and-triangulation.md +238 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/03-obvious-implementation-and-refactoring.md +228 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/04-version-control-and-conventional-commits.md +112 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/05-package-management-and-static-analysis.md +287 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/06-task-runner-and-ci-cd.md +248 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/07-encapsulation-and-polymorphism.md +279 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/08-design-patterns.md +329 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/09-solid-principles-and-module-design.md +196 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/10-higher-order-functions-and-composition.md +175 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/11-immutable-data-and-pipeline.md +233 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/12-error-handling-and-type-safety.md +398 -0
- package/lib/assets/docs/article/getting-start-tdd/ruby/index.md +83 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/01-todo-list-and-first-test.md +211 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/02-fake-it-and-triangulation.md +264 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/03-obvious-implementation-and-refactoring.md +233 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/04-version-control-and-conventional-commits.md +92 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/05-package-management-and-static-analysis.md +212 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/06-task-runner-and-ci-cd.md +164 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/07-encapsulation-and-polymorphism.md +142 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/08-design-patterns.md +145 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/09-solid-principles-and-module-design.md +110 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/10-higher-order-functions-and-composition.md +94 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/11-immutable-data-and-pipeline.md +105 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/12-error-handling-and-type-safety.md +112 -0
- package/lib/assets/docs/article/getting-start-tdd/rust/index.md +83 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/01-todo-list-and-first-test.md +111 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/02-fake-it-and-triangulation.md +107 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/03-obvious-implementation-and-refactoring.md +99 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/04-version-control-and-conventional-commits.md +123 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/05-package-management-and-static-analysis.md +196 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/06-task-runner-and-ci-cd.md +186 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/07-case-classes-and-traits.md +139 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/08-pattern-matching-and-sealed-traits.md +106 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/09-packages-and-module-design.md +75 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/10-higher-order-functions-and-composition.md +104 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/11-collections-and-lazy-evaluation.md +94 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/12-error-handling-and-type-safety.md +92 -0
- package/lib/assets/docs/article/getting-start-tdd/scala/index.md +65 -0
- package/lib/assets/docs/article/grokking-concurrency/all/index.md +404 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-1-ch02-sequential.md +554 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-2-ch04-05-threads.md +469 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-3-ch06-multitasking.md +520 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-4-ch07-parallel-patterns.md +420 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-5-ch08-09-synchronization.md +510 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-6-ch10-11-nonblocking-io.md +435 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-7-ch12-async.md +465 -0
- package/lib/assets/docs/article/grokking-concurrency/all/part-8-ch13-mapreduce.md +377 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/index.md +116 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-1.md +108 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-2.md +101 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-3.md +122 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-4.md +123 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-5.md +118 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-6.md +89 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-7.md +100 -0
- package/lib/assets/docs/article/grokking-concurrency/clojure/part-8.md +120 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/index.md +101 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-1.md +97 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-2.md +123 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-3.md +101 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-4.md +112 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-5.md +99 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-6.md +61 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-7.md +84 -0
- package/lib/assets/docs/article/grokking-concurrency/csharp/part-8.md +92 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/index.md +65 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-1.md +80 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-2.md +103 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-3.md +94 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-4.md +110 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-5.md +104 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-6.md +93 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-7.md +121 -0
- package/lib/assets/docs/article/grokking-concurrency/fsharp/part-8.md +107 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/index.md +248 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-1.md +96 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-2.md +96 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-3.md +91 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-4.md +106 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-5.md +99 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-6.md +95 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-7.md +111 -0
- package/lib/assets/docs/article/grokking-concurrency/haskell/part-8.md +118 -0
- package/lib/assets/docs/article/grokking-concurrency/index.md +66 -0
- package/lib/assets/docs/article/grokking-concurrency/java/index.md +102 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-1.md +308 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-2.md +334 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-3.md +221 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-4.md +213 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-5.md +112 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-6.md +69 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-7.md +101 -0
- package/lib/assets/docs/article/grokking-concurrency/java/part-8.md +101 -0
- package/lib/assets/docs/article/grokking-concurrency/python/index.md +313 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-1.md +239 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-2.md +418 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-3.md +227 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-4.md +299 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-5.md +315 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-6.md +297 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-7.md +314 -0
- package/lib/assets/docs/article/grokking-concurrency/python/part-8.md +360 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/index.md +270 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-1.md +108 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-2.md +120 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-3.md +126 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-4.md +175 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-5.md +158 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-6.md +94 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-7.md +133 -0
- package/lib/assets/docs/article/grokking-concurrency/rust/part-8.md +155 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/index.md +69 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-1.md +78 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-2.md +112 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-3.md +93 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-4.md +110 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-5.md +119 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-6.md +83 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-7.md +131 -0
- package/lib/assets/docs/article/grokking-concurrency/scala/part-8.md +129 -0
- package/lib/assets/docs/article/grokkingfp/all/index.md +368 -0
- package/lib/assets/docs/article/grokkingfp/all/part-1-ch01-fp-introduction.md +530 -0
- package/lib/assets/docs/article/grokkingfp/all/part-1-ch02-pure-functions.md +923 -0
- package/lib/assets/docs/article/grokkingfp/all/part-2-ch03-immutable-data.md +1122 -0
- package/lib/assets/docs/article/grokkingfp/all/part-2-ch04-higher-order-functions.md +1104 -0
- package/lib/assets/docs/article/grokkingfp/all/part-2-ch05-flatmap.md +1026 -0
- package/lib/assets/docs/article/grokkingfp/all/part-3-ch06-option.md +777 -0
- package/lib/assets/docs/article/grokkingfp/all/part-3-ch07-either-adt.md +871 -0
- package/lib/assets/docs/article/grokkingfp/all/part-4-ch08-io-monad.md +972 -0
- package/lib/assets/docs/article/grokkingfp/all/part-4-ch09-streams.md +926 -0
- package/lib/assets/docs/article/grokkingfp/all/part-5-ch10-concurrency.md +870 -0
- package/lib/assets/docs/article/grokkingfp/all/part-6-ch11-application.md +715 -0
- package/lib/assets/docs/article/grokkingfp/all/part-6-ch12-testing.md +626 -0
- package/lib/assets/docs/article/grokkingfp/all/writing-plan.md +696 -0
- package/lib/assets/docs/article/grokkingfp/clojure/index.md +276 -0
- package/lib/assets/docs/article/grokkingfp/clojure/part-1.md +667 -0
- package/lib/assets/docs/article/grokkingfp/clojure/part-2.md +643 -0
- package/lib/assets/docs/article/grokkingfp/clojure/part-3.md +620 -0
- package/lib/assets/docs/article/grokkingfp/clojure/part-4.md +697 -0
- package/lib/assets/docs/article/grokkingfp/clojure/part-5.md +751 -0
- package/lib/assets/docs/article/grokkingfp/clojure/part-6.md +721 -0
- package/lib/assets/docs/article/grokkingfp/csharp/index.md +246 -0
- package/lib/assets/docs/article/grokkingfp/csharp/part-1.md +811 -0
- package/lib/assets/docs/article/grokkingfp/csharp/part-2.md +971 -0
- package/lib/assets/docs/article/grokkingfp/csharp/part-3.md +981 -0
- package/lib/assets/docs/article/grokkingfp/csharp/part-4.md +949 -0
- package/lib/assets/docs/article/grokkingfp/csharp/part-5.md +947 -0
- package/lib/assets/docs/article/grokkingfp/csharp/part-6.md +739 -0
- package/lib/assets/docs/article/grokkingfp/elixir/index.md +203 -0
- package/lib/assets/docs/article/grokkingfp/elixir/part-1.md +710 -0
- package/lib/assets/docs/article/grokkingfp/elixir/part-2.md +838 -0
- package/lib/assets/docs/article/grokkingfp/elixir/part-3.md +985 -0
- package/lib/assets/docs/article/grokkingfp/elixir/part-4.md +974 -0
- package/lib/assets/docs/article/grokkingfp/elixir/part-5.md +1284 -0
- package/lib/assets/docs/article/grokkingfp/elixir/part-6.md +1047 -0
- package/lib/assets/docs/article/grokkingfp/fsharp/index.md +210 -0
- package/lib/assets/docs/article/grokkingfp/fsharp/part-1.md +714 -0
- package/lib/assets/docs/article/grokkingfp/fsharp/part-2.md +961 -0
- package/lib/assets/docs/article/grokkingfp/fsharp/part-3.md +972 -0
- package/lib/assets/docs/article/grokkingfp/fsharp/part-4.md +832 -0
- package/lib/assets/docs/article/grokkingfp/fsharp/part-5.md +911 -0
- package/lib/assets/docs/article/grokkingfp/fsharp/part-6.md +920 -0
- package/lib/assets/docs/article/grokkingfp/haskell/index.md +234 -0
- package/lib/assets/docs/article/grokkingfp/haskell/part-1.md +591 -0
- package/lib/assets/docs/article/grokkingfp/haskell/part-2.md +866 -0
- package/lib/assets/docs/article/grokkingfp/haskell/part-3.md +915 -0
- package/lib/assets/docs/article/grokkingfp/haskell/part-4.md +876 -0
- package/lib/assets/docs/article/grokkingfp/haskell/part-5.md +845 -0
- package/lib/assets/docs/article/grokkingfp/haskell/part-6.md +842 -0
- package/lib/assets/docs/article/grokkingfp/index.md +143 -0
- package/lib/assets/docs/article/grokkingfp/java/index.md +211 -0
- package/lib/assets/docs/article/grokkingfp/java/part-1.md +646 -0
- package/lib/assets/docs/article/grokkingfp/java/part-2.md +667 -0
- package/lib/assets/docs/article/grokkingfp/java/part-3.md +672 -0
- package/lib/assets/docs/article/grokkingfp/java/part-4.md +771 -0
- package/lib/assets/docs/article/grokkingfp/java/part-5.md +959 -0
- package/lib/assets/docs/article/grokkingfp/java/part-6.md +1324 -0
- package/lib/assets/docs/article/grokkingfp/python/index.md +258 -0
- package/lib/assets/docs/article/grokkingfp/python/part-1.md +437 -0
- package/lib/assets/docs/article/grokkingfp/python/part-2.md +958 -0
- package/lib/assets/docs/article/grokkingfp/python/part-3.md +1004 -0
- package/lib/assets/docs/article/grokkingfp/python/part-4.md +765 -0
- package/lib/assets/docs/article/grokkingfp/python/part-5.md +747 -0
- package/lib/assets/docs/article/grokkingfp/python/part-6.md +861 -0
- package/lib/assets/docs/article/grokkingfp/ruby/index.md +330 -0
- package/lib/assets/docs/article/grokkingfp/ruby/part-1.md +753 -0
- package/lib/assets/docs/article/grokkingfp/ruby/part-2.md +938 -0
- package/lib/assets/docs/article/grokkingfp/ruby/part-3.md +946 -0
- package/lib/assets/docs/article/grokkingfp/ruby/part-4.md +921 -0
- package/lib/assets/docs/article/grokkingfp/ruby/part-5.md +908 -0
- package/lib/assets/docs/article/grokkingfp/ruby/part-6.md +1410 -0
- package/lib/assets/docs/article/grokkingfp/rust/index.md +242 -0
- package/lib/assets/docs/article/grokkingfp/rust/part-1.md +634 -0
- package/lib/assets/docs/article/grokkingfp/rust/part-2.md +1060 -0
- package/lib/assets/docs/article/grokkingfp/rust/part-3.md +994 -0
- package/lib/assets/docs/article/grokkingfp/rust/part-4.md +571 -0
- package/lib/assets/docs/article/grokkingfp/rust/part-5.md +705 -0
- package/lib/assets/docs/article/grokkingfp/rust/part-6.md +508 -0
- package/lib/assets/docs/article/grokkingfp/scala/index.md +171 -0
- package/lib/assets/docs/article/grokkingfp/scala/part-1.md +541 -0
- package/lib/assets/docs/article/grokkingfp/scala/part-2.md +946 -0
- package/lib/assets/docs/article/grokkingfp/scala/part-3.md +917 -0
- package/lib/assets/docs/article/grokkingfp/scala/part-4.md +742 -0
- package/lib/assets/docs/article/grokkingfp/scala/part-5.md +722 -0
- package/lib/assets/docs/article/grokkingfp/scala/part-6.md +865 -0
- package/lib/assets/docs/article/grokkingfp/typescript/index.md +273 -0
- package/lib/assets/docs/article/grokkingfp/typescript/part-1.md +559 -0
- package/lib/assets/docs/article/grokkingfp/typescript/part-2.md +1129 -0
- package/lib/assets/docs/article/grokkingfp/typescript/part-3.md +842 -0
- package/lib/assets/docs/article/grokkingfp/typescript/part-4.md +1085 -0
- package/lib/assets/docs/article/grokkingfp/typescript/part-5.md +717 -0
- package/lib/assets/docs/article/grokkingfp/typescript/part-6.md +980 -0
- package/lib/assets/docs/article/index.md +36 -0
- package/lib/assets/docs/design/index.md +39 -27
- package/lib/assets/docs/development/index.md +11 -1
- package/lib/assets/docs/index.md +33 -106
- package/lib/assets/docs/operation/index.md +16 -6
- package/lib/assets/docs/reference/index.md +5 -4
- package/lib/assets/docs/requirements/index.md +13 -6
- package/lib/assets/docs/strategy/index.md +11 -4
- package/lib/assets/docs/template/index.md +9 -5
- package/lib/assets/mkdocs.yml +29 -17
- package/package.json +1 -1
|
@@ -0,0 +1,1324 @@
|
|
|
1
|
+
# Part VI: 実践的なアプリケーション構築とテスト
|
|
2
|
+
|
|
3
|
+
本章では、これまで学んだ関数型プログラミングの概念を統合し、実践的なアプリケーションを構築します。また、関数型プログラミングにおけるテスト戦略についても学びます。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 第11章: 実践的なアプリケーション構築
|
|
8
|
+
|
|
9
|
+
### 11.1 TravelGuide アプリケーション
|
|
10
|
+
|
|
11
|
+
旅行ガイドアプリケーションを例に、実践的な FP アプリケーションの構築方法を学びます。
|
|
12
|
+
|
|
13
|
+
```plantuml
|
|
14
|
+
@startuml
|
|
15
|
+
!theme plain
|
|
16
|
+
|
|
17
|
+
package "TravelGuide Application" {
|
|
18
|
+
rectangle "Model" {
|
|
19
|
+
class Location {
|
|
20
|
+
id: LocationId
|
|
21
|
+
name: String
|
|
22
|
+
population: int
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class Attraction {
|
|
26
|
+
name: String
|
|
27
|
+
description: Option<String>
|
|
28
|
+
location: Location
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class Guide {
|
|
32
|
+
attraction: Attraction
|
|
33
|
+
subjects: List<PopCultureSubject>
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rectangle "Data Access" {
|
|
38
|
+
interface DataAccess {
|
|
39
|
+
+findAttractions(): IO<List<Attraction>>
|
|
40
|
+
+findArtistsFromLocation(): IO<List<Artist>>
|
|
41
|
+
+findMoviesAboutLocation(): IO<List<Movie>>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class WikidataDataAccess
|
|
45
|
+
class CachedDataAccess
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
DataAccess <|.. WikidataDataAccess
|
|
49
|
+
DataAccess <|.. CachedDataAccess
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@enduml
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 11.2 ドメインモデルの定義
|
|
56
|
+
|
|
57
|
+
**ソースファイル**: `app/java/src/main/java/ch11/TravelGuide.java`
|
|
58
|
+
|
|
59
|
+
```java
|
|
60
|
+
// 位置情報を表す値オブジェクト
|
|
61
|
+
public record LocationId(String value) {
|
|
62
|
+
public LocationId {
|
|
63
|
+
Objects.requireNonNull(value, "LocationId value cannot be null");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ロケーション
|
|
68
|
+
public record Location(LocationId id, String name, int population) {
|
|
69
|
+
public Location {
|
|
70
|
+
Objects.requireNonNull(id, "Location id cannot be null");
|
|
71
|
+
Objects.requireNonNull(name, "Location name cannot be null");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// アトラクション(観光地)
|
|
76
|
+
public record Attraction(
|
|
77
|
+
String name,
|
|
78
|
+
Option<String> description,
|
|
79
|
+
Location location
|
|
80
|
+
) {
|
|
81
|
+
public Attraction {
|
|
82
|
+
Objects.requireNonNull(name, "Attraction name cannot be null");
|
|
83
|
+
Objects.requireNonNull(description, "Attraction description cannot be null");
|
|
84
|
+
Objects.requireNonNull(location, "Attraction location cannot be null");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ポップカルチャーの題材(sealed interface)
|
|
89
|
+
public sealed interface PopCultureSubject permits Artist, Movie {
|
|
90
|
+
String name();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// アーティスト
|
|
94
|
+
public record Artist(String name, int followers) implements PopCultureSubject {
|
|
95
|
+
public Artist {
|
|
96
|
+
Objects.requireNonNull(name, "Artist name cannot be null");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 映画
|
|
101
|
+
public record Movie(String name, int boxOffice) implements PopCultureSubject {
|
|
102
|
+
public Movie {
|
|
103
|
+
Objects.requireNonNull(name, "Movie name cannot be null");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 旅行ガイド
|
|
108
|
+
public record Guide(
|
|
109
|
+
Attraction attraction,
|
|
110
|
+
List<PopCultureSubject> subjects
|
|
111
|
+
) {
|
|
112
|
+
public Guide {
|
|
113
|
+
Objects.requireNonNull(attraction, "Guide attraction cannot be null");
|
|
114
|
+
Objects.requireNonNull(subjects, "Guide subjects cannot be null");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Java 17 の `record` と `sealed interface` を活用して、Scala の case class と ADT を表現しています。
|
|
120
|
+
|
|
121
|
+
### 11.3 データアクセス層の抽象化
|
|
122
|
+
|
|
123
|
+
外部データソースへのアクセスをインターフェースで抽象化します。
|
|
124
|
+
|
|
125
|
+
**ソースファイル**: `app/java/src/main/java/ch11/DataAccess.java`
|
|
126
|
+
|
|
127
|
+
```java
|
|
128
|
+
public interface DataAccess {
|
|
129
|
+
IO<List<Attraction>> findAttractions(
|
|
130
|
+
String name,
|
|
131
|
+
AttractionOrdering ordering,
|
|
132
|
+
int limit
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
IO<List<Artist>> findArtistsFromLocation(
|
|
136
|
+
LocationId locationId,
|
|
137
|
+
int limit
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
IO<List<Movie>> findMoviesAboutLocation(
|
|
141
|
+
LocationId locationId,
|
|
142
|
+
int limit
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```plantuml
|
|
148
|
+
@startuml
|
|
149
|
+
!theme plain
|
|
150
|
+
|
|
151
|
+
interface DataAccess {
|
|
152
|
+
+findAttractions(): IO<List<Attraction>>
|
|
153
|
+
+findArtistsFromLocation(): IO<List<Artist>>
|
|
154
|
+
+findMoviesAboutLocation(): IO<List<Movie>>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
note right of DataAccess
|
|
158
|
+
純粋な関数型インターフェース
|
|
159
|
+
すべての操作は IO でラップ
|
|
160
|
+
end note
|
|
161
|
+
|
|
162
|
+
class WikidataDataAccess {
|
|
163
|
+
-sparqlEndpoint: String
|
|
164
|
+
+findAttractions(): IO<List<Attraction>>
|
|
165
|
+
+findArtistsFromLocation(): IO<List<Artist>>
|
|
166
|
+
+findMoviesAboutLocation(): IO<List<Movie>>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class TestDataAccess {
|
|
170
|
+
-testData: Map<String, List<Attraction>>
|
|
171
|
+
+findAttractions(): IO<List<Attraction>>
|
|
172
|
+
+findArtistsFromLocation(): IO<List<Artist>>
|
|
173
|
+
+findMoviesAboutLocation(): IO<List<Movie>>
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
DataAccess <|.. WikidataDataAccess
|
|
177
|
+
DataAccess <|.. TestDataAccess
|
|
178
|
+
|
|
179
|
+
@enduml
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 11.4 Resource によるリソース管理
|
|
183
|
+
|
|
184
|
+
**ソースファイル**: `app/java/src/main/java/ch11/Resource.java`
|
|
185
|
+
|
|
186
|
+
`Resource` は、リソースの取得(acquire)と解放(release)を安全に管理するための型です。cats-effect の `Resource` に相当する機能を提供します。
|
|
187
|
+
|
|
188
|
+
```java
|
|
189
|
+
public final class Resource<A> {
|
|
190
|
+
|
|
191
|
+
private final IO<A> acquire;
|
|
192
|
+
private final Consumer<A> release;
|
|
193
|
+
|
|
194
|
+
private Resource(IO<A> acquire, Consumer<A> release) {
|
|
195
|
+
this.acquire = acquire;
|
|
196
|
+
this.release = release;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// リソースを作成
|
|
200
|
+
public static <A> Resource<A> make(IO<A> acquire, Consumer<A> release) {
|
|
201
|
+
return new Resource<>(acquire, release);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// AutoCloseable からリソースを作成
|
|
205
|
+
public static <A extends AutoCloseable> Resource<A> fromAutoCloseable(IO<A> acquire) {
|
|
206
|
+
return new Resource<>(acquire, a -> {
|
|
207
|
+
try {
|
|
208
|
+
a.close();
|
|
209
|
+
} catch (Exception e) {
|
|
210
|
+
throw new RuntimeException("Failed to close resource", e);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// リソースを使用して処理を実行
|
|
216
|
+
// リソースは処理完了後(成功・失敗問わず)に必ず解放される
|
|
217
|
+
public <B> IO<B> use(Function<A, IO<B>> f) {
|
|
218
|
+
return acquire.flatMap(resource ->
|
|
219
|
+
f.apply(resource)
|
|
220
|
+
.guarantee(IO.effect(() -> release.accept(resource)))
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Resource の使用例
|
|
227
|
+
|
|
228
|
+
```java
|
|
229
|
+
// ファイルリソースの例
|
|
230
|
+
Resource<BufferedReader> fileResource = Resource.fromAutoCloseable(
|
|
231
|
+
IO.delay(() -> new BufferedReader(new FileReader("data.txt")))
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// リソースを使用して処理を実行
|
|
235
|
+
IO<String> readFirstLine = fileResource.use(reader ->
|
|
236
|
+
IO.delay(reader::readLine)
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// ファイルは自動的にクローズされる
|
|
240
|
+
String firstLine = readFirstLine.unsafeRun();
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```plantuml
|
|
244
|
+
@startuml
|
|
245
|
+
!theme plain
|
|
246
|
+
|
|
247
|
+
participant "Application" as app
|
|
248
|
+
participant "Resource" as res
|
|
249
|
+
participant "BufferedReader" as reader
|
|
250
|
+
participant "File" as file
|
|
251
|
+
|
|
252
|
+
app -> res: Resource.make(acquire)(release)
|
|
253
|
+
res -> reader: acquire: IO.delay(new BufferedReader(...))
|
|
254
|
+
reader -> file: open
|
|
255
|
+
file --> reader: file handle
|
|
256
|
+
|
|
257
|
+
app -> res: use (read operation)
|
|
258
|
+
res -> reader: readLine()
|
|
259
|
+
reader -> file: read
|
|
260
|
+
file --> reader: content
|
|
261
|
+
reader --> res: String
|
|
262
|
+
res --> app: result
|
|
263
|
+
|
|
264
|
+
app -> res: release (automatic)
|
|
265
|
+
res -> reader: close()
|
|
266
|
+
reader -> file: close
|
|
267
|
+
file --> reader: done
|
|
268
|
+
|
|
269
|
+
note over res
|
|
270
|
+
Resource は例外が発生しても
|
|
271
|
+
必ず release を実行する
|
|
272
|
+
(guarantee を使用)
|
|
273
|
+
end note
|
|
274
|
+
|
|
275
|
+
@enduml
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### guarantee メソッド
|
|
279
|
+
|
|
280
|
+
`IO` クラスに追加した `guarantee` メソッドは、成功・失敗にかかわらず必ず実行される処理を指定します。
|
|
281
|
+
|
|
282
|
+
```java
|
|
283
|
+
// IO クラスの guarantee メソッド
|
|
284
|
+
public IO<A> guarantee(IO<Void> finalizer) {
|
|
285
|
+
return new IO<>(() -> {
|
|
286
|
+
try {
|
|
287
|
+
return thunk.get();
|
|
288
|
+
} finally {
|
|
289
|
+
try {
|
|
290
|
+
finalizer.unsafeRun();
|
|
291
|
+
} catch (Exception ignored) {
|
|
292
|
+
// finalizer のエラーは無視
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 11.5 キャッシュの実装
|
|
300
|
+
|
|
301
|
+
**ソースファイル**: `app/java/src/main/java/ch11/CachedDataAccess.java`
|
|
302
|
+
|
|
303
|
+
`Ref` を使用したスレッドセーフなキャッシュの実装:
|
|
304
|
+
|
|
305
|
+
```java
|
|
306
|
+
public final class CachedDataAccess implements DataAccess {
|
|
307
|
+
|
|
308
|
+
private final DataAccess underlying;
|
|
309
|
+
private final Ref<Map<String, Object>> cache;
|
|
310
|
+
|
|
311
|
+
private CachedDataAccess(DataAccess underlying, Ref<Map<String, Object>> cache) {
|
|
312
|
+
this.underlying = underlying;
|
|
313
|
+
this.cache = cache;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// IO 経由でキャッシュ付き DataAccess を作成
|
|
317
|
+
public static IO<DataAccess> create(DataAccess underlying) {
|
|
318
|
+
return Ref.<Map<String, Object>>of(HashMap.empty())
|
|
319
|
+
.map(cache -> new CachedDataAccess(underlying, cache));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@Override
|
|
323
|
+
public IO<List<Attraction>> findAttractions(
|
|
324
|
+
String name,
|
|
325
|
+
AttractionOrdering ordering,
|
|
326
|
+
int limit) {
|
|
327
|
+
String key = "attractions:" + name + ":" + ordering + ":" + limit;
|
|
328
|
+
return getOrCompute(key, () -> underlying.findAttractions(name, ordering, limit));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@SuppressWarnings("unchecked")
|
|
332
|
+
private <T> IO<T> getOrCompute(String key, Supplier<IO<T>> compute) {
|
|
333
|
+
return cache.get()
|
|
334
|
+
.flatMap(currentCache ->
|
|
335
|
+
currentCache.get(key)
|
|
336
|
+
.map(cached -> IO.pure((T) cached))
|
|
337
|
+
.getOrElse(() ->
|
|
338
|
+
compute.get().flatMap(result ->
|
|
339
|
+
cache.update(c -> c.put(key, result))
|
|
340
|
+
.andThen(IO.pure(result))
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ... 他のメソッドも同様
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```plantuml
|
|
351
|
+
@startuml
|
|
352
|
+
!theme plain
|
|
353
|
+
skinparam activity {
|
|
354
|
+
BackgroundColor White
|
|
355
|
+
BorderColor Black
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
start
|
|
359
|
+
|
|
360
|
+
:キャッシュキー生成;
|
|
361
|
+
|
|
362
|
+
:cache.get でキャッシュ確認;
|
|
363
|
+
|
|
364
|
+
if (キャッシュにデータあり?) then (yes)
|
|
365
|
+
:キャッシュからデータ返却;
|
|
366
|
+
else (no)
|
|
367
|
+
:外部 API を呼び出し;
|
|
368
|
+
:結果をキャッシュに保存;
|
|
369
|
+
:結果を返却;
|
|
370
|
+
endif
|
|
371
|
+
|
|
372
|
+
stop
|
|
373
|
+
|
|
374
|
+
note right
|
|
375
|
+
Ref を使用した
|
|
376
|
+
スレッドセーフなキャッシュ
|
|
377
|
+
end note
|
|
378
|
+
|
|
379
|
+
@enduml
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 11.6 ガイドスコアの計算
|
|
383
|
+
|
|
384
|
+
純粋関数でガイドのスコアを計算します:
|
|
385
|
+
|
|
386
|
+
```java
|
|
387
|
+
/**
|
|
388
|
+
* ガイドのスコアを計算
|
|
389
|
+
*
|
|
390
|
+
* 要件:
|
|
391
|
+
* - 30 点: 説明がある場合
|
|
392
|
+
* - 10 点/件: アーティストまたは映画(最大 40 点)
|
|
393
|
+
* - 1 点/100,000 フォロワー: 全アーティスト合計(最大 15 点)
|
|
394
|
+
* - 1 点/10,000,000 ドル: 全映画の興行収入合計(最大 15 点)
|
|
395
|
+
*/
|
|
396
|
+
public static int guideScore(Guide guide) {
|
|
397
|
+
int descriptionScore = guide.attraction().description().isDefined() ? 30 : 0;
|
|
398
|
+
int quantityScore = Math.min(40, guide.subjects().size() * 10);
|
|
399
|
+
|
|
400
|
+
// Long を使用してオーバーフローを防ぐ
|
|
401
|
+
long totalFollowers = guide.subjects()
|
|
402
|
+
.filter(s -> s instanceof Artist)
|
|
403
|
+
.map(s -> (long) ((Artist) s).followers())
|
|
404
|
+
.sum().longValue();
|
|
405
|
+
|
|
406
|
+
long totalBoxOffice = guide.subjects()
|
|
407
|
+
.filter(s -> s instanceof Movie)
|
|
408
|
+
.map(s -> (long) ((Movie) s).boxOffice())
|
|
409
|
+
.sum().longValue();
|
|
410
|
+
|
|
411
|
+
int followersScore = (int) Math.min(15, totalFollowers / 100_000);
|
|
412
|
+
int boxOfficeScore = (int) Math.min(15, totalBoxOffice / 10_000_000);
|
|
413
|
+
|
|
414
|
+
return descriptionScore + quantityScore + followersScore + boxOfficeScore;
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
重要なポイント:
|
|
419
|
+
- **オーバーフロー防止**: `long` を使用して大きな数値の合計を安全に計算
|
|
420
|
+
- **上限値の設定**: 各スコア成分に最大値を設定してスコアを 0-100 の範囲に
|
|
421
|
+
|
|
422
|
+
### 11.7 アプリケーションの組み立て
|
|
423
|
+
|
|
424
|
+
すべてのコンポーネントを組み合わせてアプリケーションを構築します。
|
|
425
|
+
|
|
426
|
+
#### バージョン 1: 基本的な実装
|
|
427
|
+
|
|
428
|
+
```java
|
|
429
|
+
public static IO<Option<Guide>> travelGuideV1(
|
|
430
|
+
DataAccess dataAccess,
|
|
431
|
+
String attractionName) {
|
|
432
|
+
|
|
433
|
+
return dataAccess.findAttractions(attractionName, AttractionOrdering.BY_LOCATION_POPULATION, 1)
|
|
434
|
+
.flatMap(attractions -> attractions.headOption()
|
|
435
|
+
.map(attraction ->
|
|
436
|
+
dataAccess.findArtistsFromLocation(attraction.location().id(), 2)
|
|
437
|
+
.flatMap(artists ->
|
|
438
|
+
dataAccess.findMoviesAboutLocation(attraction.location().id(), 2)
|
|
439
|
+
.map(movies -> {
|
|
440
|
+
List<PopCultureSubject> subjects = List.<PopCultureSubject>empty()
|
|
441
|
+
.appendAll(artists)
|
|
442
|
+
.appendAll(movies);
|
|
443
|
+
return Option.some(new Guide(attraction, subjects));
|
|
444
|
+
})
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
.getOrElse(IO.pure(Option.none()))
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### バージョン 2: 複数候補からベストを選択
|
|
453
|
+
|
|
454
|
+
```java
|
|
455
|
+
public static IO<Option<Guide>> travelGuideV2(
|
|
456
|
+
DataAccess dataAccess,
|
|
457
|
+
String attractionName) {
|
|
458
|
+
|
|
459
|
+
return dataAccess.findAttractions(attractionName, AttractionOrdering.BY_LOCATION_POPULATION, 3)
|
|
460
|
+
.flatMap(attractions ->
|
|
461
|
+
IO.traverse(attractions, attraction ->
|
|
462
|
+
dataAccess.findArtistsFromLocation(attraction.location().id(), 2)
|
|
463
|
+
.flatMap(artists ->
|
|
464
|
+
dataAccess.findMoviesAboutLocation(attraction.location().id(), 2)
|
|
465
|
+
.map(movies -> {
|
|
466
|
+
List<PopCultureSubject> subjects = List.<PopCultureSubject>empty()
|
|
467
|
+
.appendAll(artists)
|
|
468
|
+
.appendAll(movies);
|
|
469
|
+
return new Guide(attraction, subjects);
|
|
470
|
+
})
|
|
471
|
+
)
|
|
472
|
+
).map(TravelGuide::findBestGuide)
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### バージョン 3: エラーハンドリング付き
|
|
478
|
+
|
|
479
|
+
```java
|
|
480
|
+
public static IO<Either<SearchReport, Guide>> travelGuideV3(
|
|
481
|
+
DataAccess dataAccess,
|
|
482
|
+
String attractionName) {
|
|
483
|
+
|
|
484
|
+
return dataAccess.findAttractions(attractionName, AttractionOrdering.BY_LOCATION_POPULATION, 3)
|
|
485
|
+
.attempt()
|
|
486
|
+
.flatMap(attractionsResult ->
|
|
487
|
+
attractionsResult.fold(
|
|
488
|
+
error -> IO.pure(Either.left(
|
|
489
|
+
SearchReport.withProblems(List.of(error.getMessage())))),
|
|
490
|
+
attractions ->
|
|
491
|
+
IO.traverse(attractions, attraction ->
|
|
492
|
+
guideForAttraction(dataAccess, attraction).attempt()
|
|
493
|
+
)
|
|
494
|
+
.map(TravelGuide::processGuideResults)
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
```plantuml
|
|
501
|
+
@startuml
|
|
502
|
+
!theme plain
|
|
503
|
+
|
|
504
|
+
rectangle "travelGuide 関数" {
|
|
505
|
+
card "1. アトラクション検索" as step1
|
|
506
|
+
card "2. アーティスト検索" as step2
|
|
507
|
+
card "3. 映画検索" as step3
|
|
508
|
+
card "4. Guide 組み立て" as step4
|
|
509
|
+
card "5. ベストを選択" as step5
|
|
510
|
+
|
|
511
|
+
step1 --> step2 : attraction
|
|
512
|
+
step1 --> step3 : location.id
|
|
513
|
+
step2 --> step4 : artists
|
|
514
|
+
step3 --> step4 : movies
|
|
515
|
+
step4 --> step5 : guides
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
note bottom
|
|
519
|
+
IO.traverse で
|
|
520
|
+
複数の IO を合成
|
|
521
|
+
end note
|
|
522
|
+
|
|
523
|
+
@enduml
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### 11.8 attempt メソッドによるエラーハンドリング
|
|
527
|
+
|
|
528
|
+
`IO` クラスに追加した `attempt` メソッドは、例外を `Either` に変換します:
|
|
529
|
+
|
|
530
|
+
```java
|
|
531
|
+
// IO クラスの attempt メソッド
|
|
532
|
+
public IO<Either<Throwable, A>> attempt() {
|
|
533
|
+
return new IO<>(() -> {
|
|
534
|
+
try {
|
|
535
|
+
return Either.right(thunk.get());
|
|
536
|
+
} catch (Throwable e) {
|
|
537
|
+
return Either.left(e);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
これにより、例外を値として扱うことができ、関数型の方法でエラーハンドリングができます。
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## 第12章: テスト戦略
|
|
548
|
+
|
|
549
|
+
### 12.1 関数型プログラミングのテスト
|
|
550
|
+
|
|
551
|
+
関数型プログラミングでは、純粋関数のおかげでテストが非常に簡単になります。
|
|
552
|
+
|
|
553
|
+
```plantuml
|
|
554
|
+
@startuml
|
|
555
|
+
!theme plain
|
|
556
|
+
|
|
557
|
+
rectangle "テストの種類" {
|
|
558
|
+
rectangle "単体テスト" as unit {
|
|
559
|
+
card "純粋関数のテスト"
|
|
560
|
+
card "高速・独立"
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
rectangle "プロパティベーステスト" as property {
|
|
564
|
+
card "ランダム入力"
|
|
565
|
+
card "不変条件の検証"
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
rectangle "統合テスト" as integration {
|
|
569
|
+
card "コンポーネント連携"
|
|
570
|
+
card "スタブ使用"
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
unit --> property : 補完
|
|
575
|
+
property --> integration : 補完
|
|
576
|
+
|
|
577
|
+
@enduml
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### 12.2 SearchReport の導入
|
|
581
|
+
|
|
582
|
+
**ソースファイル**: `app/java/src/main/java/ch11/TravelGuide.java`
|
|
583
|
+
|
|
584
|
+
テスト可能性を高めるため、`SearchReport` を導入します。
|
|
585
|
+
|
|
586
|
+
```java
|
|
587
|
+
/**
|
|
588
|
+
* 検索レポート(エラーや不十分な結果を報告)
|
|
589
|
+
*/
|
|
590
|
+
public record SearchReport(
|
|
591
|
+
List<Guide> badGuides,
|
|
592
|
+
List<String> problems
|
|
593
|
+
) {
|
|
594
|
+
public SearchReport {
|
|
595
|
+
Objects.requireNonNull(badGuides, "SearchReport badGuides cannot be null");
|
|
596
|
+
Objects.requireNonNull(problems, "SearchReport problems cannot be null");
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
public static SearchReport empty() {
|
|
600
|
+
return new SearchReport(List.empty(), List.empty());
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
public static SearchReport withProblems(List<String> problems) {
|
|
604
|
+
return new SearchReport(List.empty(), problems);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
public static SearchReport withBadGuides(List<Guide> badGuides) {
|
|
608
|
+
return new SearchReport(badGuides, List.empty());
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
```plantuml
|
|
614
|
+
@startuml
|
|
615
|
+
!theme plain
|
|
616
|
+
|
|
617
|
+
class Guide {
|
|
618
|
+
attraction: Attraction
|
|
619
|
+
subjects: List<PopCultureSubject>
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
class SearchReport {
|
|
623
|
+
badGuides: List<Guide>
|
|
624
|
+
problems: List<String>
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
Guide --> SearchReport : 参照される
|
|
628
|
+
|
|
629
|
+
note right of SearchReport
|
|
630
|
+
検索の結果情報と
|
|
631
|
+
エラー情報を保持
|
|
632
|
+
end note
|
|
633
|
+
|
|
634
|
+
@enduml
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### 12.3 TestDataAccess - テスト用スタブ
|
|
638
|
+
|
|
639
|
+
**ソースファイル**: `app/java/src/test/java/ch12/TestDataAccess.java`
|
|
640
|
+
|
|
641
|
+
Builder パターンを使用したテスト用スタブ:
|
|
642
|
+
|
|
643
|
+
```java
|
|
644
|
+
public class TestDataAccess implements DataAccess {
|
|
645
|
+
|
|
646
|
+
// ビルダーパターンで作成
|
|
647
|
+
public static Builder builder() {
|
|
648
|
+
return new Builder();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 空の DataAccess を作成
|
|
652
|
+
public static DataAccess empty() {
|
|
653
|
+
return builder().build();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// ファクトリメソッド
|
|
657
|
+
public static DataAccess withAttractions(Attraction... attractions) {
|
|
658
|
+
return builder().withAttractions(attractions).build();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
public static class Builder {
|
|
662
|
+
private List<Attraction> attractions = List.empty();
|
|
663
|
+
private List<Artist> artists = List.empty();
|
|
664
|
+
private List<Movie> movies = List.empty();
|
|
665
|
+
private String attractionsError = null;
|
|
666
|
+
private String artistsError = null;
|
|
667
|
+
private String moviesError = null;
|
|
668
|
+
|
|
669
|
+
public Builder withAttractions(Attraction... attractions) {
|
|
670
|
+
this.attractions = List.of(attractions);
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
public Builder withArtists(Artist... artists) {
|
|
675
|
+
this.artists = List.of(artists);
|
|
676
|
+
return this;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
public Builder withMovies(Movie... movies) {
|
|
680
|
+
this.movies = List.of(movies);
|
|
681
|
+
return this;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
public Builder failOnAttractions(String errorMessage) {
|
|
685
|
+
this.attractionsError = errorMessage;
|
|
686
|
+
return this;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
public Builder failOnArtists(String errorMessage) {
|
|
690
|
+
this.artistsError = errorMessage;
|
|
691
|
+
return this;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
public Builder failOnMovies(String errorMessage) {
|
|
695
|
+
this.moviesError = errorMessage;
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
public TestDataAccess build() {
|
|
700
|
+
return new TestDataAccess(this);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
```plantuml
|
|
707
|
+
@startuml
|
|
708
|
+
!theme plain
|
|
709
|
+
|
|
710
|
+
rectangle "テスト構成" {
|
|
711
|
+
rectangle "本番" as prod {
|
|
712
|
+
interface "DataAccess" as da1
|
|
713
|
+
class "WikidataDataAccess" as wda
|
|
714
|
+
da1 <|.. wda
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
rectangle "テスト" as test {
|
|
718
|
+
interface "DataAccess" as da2
|
|
719
|
+
class "TestDataAccess" as tda
|
|
720
|
+
da2 <|.. tda
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
note bottom of prod
|
|
725
|
+
実際の Wikidata に接続
|
|
726
|
+
end note
|
|
727
|
+
|
|
728
|
+
note bottom of test
|
|
729
|
+
テストデータを返す
|
|
730
|
+
IO.pure で即座に結果を返す
|
|
731
|
+
エラーシミュレーションも可能
|
|
732
|
+
end note
|
|
733
|
+
|
|
734
|
+
@enduml
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
#### TestDataAccess の使用例
|
|
738
|
+
|
|
739
|
+
```java
|
|
740
|
+
// テストデータの準備
|
|
741
|
+
static final Location LONDON = new Location(
|
|
742
|
+
new LocationId("Q84"),
|
|
743
|
+
"London",
|
|
744
|
+
8_908_081
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
static final Attraction TOWER_BRIDGE = new Attraction(
|
|
748
|
+
"Tower Bridge",
|
|
749
|
+
Option.none(),
|
|
750
|
+
LONDON
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
static final Artist QUEEN = new Artist("Queen", 2_050_559);
|
|
754
|
+
static final Movie INSIDE_OUT = new Movie("Inside Out", 857_611_174);
|
|
755
|
+
|
|
756
|
+
// 正常系のテスト
|
|
757
|
+
@Test
|
|
758
|
+
void withAllData() {
|
|
759
|
+
DataAccess dataAccess = TestDataAccess.of(
|
|
760
|
+
List.of(TOWER_BRIDGE),
|
|
761
|
+
List.of(QUEEN),
|
|
762
|
+
List.of(INSIDE_OUT)
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
assertThat(dataAccess.findAttractions("test", AttractionOrdering.BY_NAME, 10).unsafeRun())
|
|
766
|
+
.containsExactly(TOWER_BRIDGE);
|
|
767
|
+
assertThat(dataAccess.findArtistsFromLocation(LONDON.id(), 10).unsafeRun())
|
|
768
|
+
.containsExactly(QUEEN);
|
|
769
|
+
assertThat(dataAccess.findMoviesAboutLocation(LONDON.id(), 10).unsafeRun())
|
|
770
|
+
.containsExactly(INSIDE_OUT);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// エラーシミュレーション
|
|
774
|
+
@Test
|
|
775
|
+
void failOnAttractions() {
|
|
776
|
+
DataAccess dataAccess = TestDataAccess.builder()
|
|
777
|
+
.failOnAttractions("Network error")
|
|
778
|
+
.build();
|
|
779
|
+
|
|
780
|
+
assertThatThrownBy(() ->
|
|
781
|
+
dataAccess.findAttractions("test", AttractionOrdering.BY_NAME, 10).unsafeRun()
|
|
782
|
+
).isInstanceOf(RuntimeException.class)
|
|
783
|
+
.hasMessage("Network error");
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### 12.4 プロパティベーステスト
|
|
788
|
+
|
|
789
|
+
**ソースファイル**: `app/java/src/test/java/ch12/PropertyBasedTest.java`
|
|
790
|
+
|
|
791
|
+
JUnit 5 の `@RepeatedTest` を使用したプロパティベーステスト:
|
|
792
|
+
|
|
793
|
+
```java
|
|
794
|
+
// ジェネレータの定義
|
|
795
|
+
static int nonNegativeInt() {
|
|
796
|
+
return random.nextInt(Integer.MAX_VALUE);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
static String randomString() {
|
|
800
|
+
int length = random.nextInt(20) + 1;
|
|
801
|
+
StringBuilder sb = new StringBuilder();
|
|
802
|
+
for (int i = 0; i < length; i++) {
|
|
803
|
+
sb.append((char) ('a' + random.nextInt(26)));
|
|
804
|
+
}
|
|
805
|
+
return sb.toString();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
static Location randomLocation() {
|
|
809
|
+
return new Location(
|
|
810
|
+
new LocationId("Q" + nonNegativeInt(1000000)),
|
|
811
|
+
randomString(),
|
|
812
|
+
nonNegativeInt(10_000_000)
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
static Attraction randomAttraction(boolean withDescription) {
|
|
817
|
+
return new Attraction(
|
|
818
|
+
randomString(),
|
|
819
|
+
withDescription ? Option.some(randomString()) : Option.none(),
|
|
820
|
+
randomLocation()
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
static Guide randomGuide(boolean withDescription, int maxSubjects) {
|
|
825
|
+
return new Guide(
|
|
826
|
+
randomAttraction(withDescription),
|
|
827
|
+
randomSubjects(maxSubjects)
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
```plantuml
|
|
833
|
+
@startuml
|
|
834
|
+
!theme plain
|
|
835
|
+
|
|
836
|
+
rectangle "プロパティベーステスト" {
|
|
837
|
+
card "randomXxx()" as gen
|
|
838
|
+
card "ランダムデータ生成" as random
|
|
839
|
+
card "プロパティ検証" as verify
|
|
840
|
+
card "@RepeatedTest(100)" as repeat
|
|
841
|
+
|
|
842
|
+
gen --> random
|
|
843
|
+
random --> verify
|
|
844
|
+
verify --> repeat
|
|
845
|
+
repeat --> random : 繰り返し
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
note bottom
|
|
849
|
+
ランダムな入力で
|
|
850
|
+
不変条件を検証
|
|
851
|
+
end note
|
|
852
|
+
|
|
853
|
+
@enduml
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### 12.5 不変条件のテスト
|
|
857
|
+
|
|
858
|
+
```java
|
|
859
|
+
@Nested
|
|
860
|
+
@DisplayName("guideScore のプロパティ")
|
|
861
|
+
class GuideScorePropertyTest {
|
|
862
|
+
|
|
863
|
+
@RepeatedTest(100)
|
|
864
|
+
@DisplayName("スコアは常に 0 以上 100 以下")
|
|
865
|
+
void scoreIsBetween0And100() {
|
|
866
|
+
Guide guide = randomGuide(random.nextBoolean(), 10);
|
|
867
|
+
|
|
868
|
+
int score = guideScore(guide);
|
|
869
|
+
|
|
870
|
+
assertThat(score).isBetween(0, 100);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
@RepeatedTest(100)
|
|
874
|
+
@DisplayName("説明ありのスコアは説明なしより 30 点高い")
|
|
875
|
+
void descriptionAdds30Points() {
|
|
876
|
+
Location location = randomLocation();
|
|
877
|
+
List<PopCultureSubject> subjects = randomSubjects(4);
|
|
878
|
+
|
|
879
|
+
Attraction withDesc = new Attraction("Test", Option.some("Description"), location);
|
|
880
|
+
Attraction withoutDesc = new Attraction("Test", Option.none(), location);
|
|
881
|
+
|
|
882
|
+
Guide guideWithDesc = new Guide(withDesc, subjects);
|
|
883
|
+
Guide guideWithoutDesc = new Guide(withoutDesc, subjects);
|
|
884
|
+
|
|
885
|
+
int scoreDiff = guideScore(guideWithDesc) - guideScore(guideWithoutDesc);
|
|
886
|
+
|
|
887
|
+
assertThat(scoreDiff).isEqualTo(30);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
@RepeatedTest(100)
|
|
891
|
+
@DisplayName("スコアはアトラクション名に依存しない")
|
|
892
|
+
void scoreDoesNotDependOnName() {
|
|
893
|
+
Location location = randomLocation();
|
|
894
|
+
Option<String> description = Option.some(randomString());
|
|
895
|
+
List<PopCultureSubject> subjects = randomSubjects(4);
|
|
896
|
+
|
|
897
|
+
Attraction attr1 = new Attraction("Name1", description, location);
|
|
898
|
+
Attraction attr2 = new Attraction("Name2", description, location);
|
|
899
|
+
|
|
900
|
+
Guide guide1 = new Guide(attr1, subjects);
|
|
901
|
+
Guide guide2 = new Guide(attr2, subjects);
|
|
902
|
+
|
|
903
|
+
assertThat(guideScore(guide1)).isEqualTo(guideScore(guide2));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
@Test
|
|
907
|
+
@DisplayName("フォロワー数のオーバーフローを防ぐ")
|
|
908
|
+
void preventsFollowerOverflow() {
|
|
909
|
+
Attraction attraction = randomAttraction(false);
|
|
910
|
+
List<Artist> artists = List.range(0, 100)
|
|
911
|
+
.map(i -> new Artist("Artist" + i, Integer.MAX_VALUE));
|
|
912
|
+
Guide guide = new Guide(attraction, List.narrow(artists));
|
|
913
|
+
|
|
914
|
+
int score = guideScore(guide);
|
|
915
|
+
|
|
916
|
+
assertThat(score).isBetween(0, 100);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
### 12.6 純粋関数のプロパティテスト
|
|
922
|
+
|
|
923
|
+
```java
|
|
924
|
+
@Nested
|
|
925
|
+
@DisplayName("純粋関数のプロパティ")
|
|
926
|
+
class PureFunctionPropertyTest {
|
|
927
|
+
|
|
928
|
+
@RepeatedTest(100)
|
|
929
|
+
@DisplayName("guideScore は同じ入力に対して同じ出力を返す(参照透過性)")
|
|
930
|
+
void guideScoreIsReferentiallyTransparent() {
|
|
931
|
+
Guide guide = randomGuide(random.nextBoolean(), 5);
|
|
932
|
+
|
|
933
|
+
int score1 = guideScore(guide);
|
|
934
|
+
int score2 = guideScore(guide);
|
|
935
|
+
int score3 = guideScore(guide);
|
|
936
|
+
|
|
937
|
+
assertThat(score1).isEqualTo(score2).isEqualTo(score3);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
@RepeatedTest(100)
|
|
941
|
+
@DisplayName("findBestGuide は同じ入力に対して同じ出力を返す")
|
|
942
|
+
void findBestGuideIsReferentiallyTransparent() {
|
|
943
|
+
List<Guide> guides = List.range(0, random.nextInt(5) + 1)
|
|
944
|
+
.map(i -> randomGuide(random.nextBoolean(), 3));
|
|
945
|
+
|
|
946
|
+
Option<Guide> result1 = TravelGuide.findBestGuide(guides);
|
|
947
|
+
Option<Guide> result2 = TravelGuide.findBestGuide(guides);
|
|
948
|
+
|
|
949
|
+
assertThat(result1).isEqualTo(result2);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
### 12.7 キャッシュのテスト
|
|
955
|
+
|
|
956
|
+
**ソースファイル**: `app/java/src/test/java/ch11/CachedDataAccessTest.java`
|
|
957
|
+
|
|
958
|
+
```java
|
|
959
|
+
@Test
|
|
960
|
+
@DisplayName("同じクエリは2回目以降キャッシュから返される")
|
|
961
|
+
void cachedResultsAreReused() {
|
|
962
|
+
AtomicInteger callCount = new AtomicInteger(0);
|
|
963
|
+
|
|
964
|
+
DataAccess underlying = new DataAccess() {
|
|
965
|
+
@Override
|
|
966
|
+
public IO<List<Attraction>> findAttractions(String name, AttractionOrdering ordering, int limit) {
|
|
967
|
+
return IO.delay(() -> {
|
|
968
|
+
callCount.incrementAndGet();
|
|
969
|
+
return List.of(TOWER_BRIDGE);
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// 他のメソッドも同様に実装...
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
DataAccess cached = CachedDataAccess.createUnsafe(underlying);
|
|
977
|
+
|
|
978
|
+
// 1回目のクエリ
|
|
979
|
+
cached.findAttractions("Bridge", AttractionOrdering.BY_NAME, 10).unsafeRun();
|
|
980
|
+
assertThat(callCount.get()).isEqualTo(1);
|
|
981
|
+
|
|
982
|
+
// 2回目のクエリ(同じパラメータ)
|
|
983
|
+
cached.findAttractions("Bridge", AttractionOrdering.BY_NAME, 10).unsafeRun();
|
|
984
|
+
assertThat(callCount.get()).isEqualTo(1); // キャッシュから返されるので呼び出し回数は増えない
|
|
985
|
+
|
|
986
|
+
// 3回目のクエリ(異なるパラメータ)
|
|
987
|
+
cached.findAttractions("Tower", AttractionOrdering.BY_NAME, 10).unsafeRun();
|
|
988
|
+
assertThat(callCount.get()).isEqualTo(2); // 新しいクエリなので呼び出される
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### 12.8 Resource のテスト
|
|
993
|
+
|
|
994
|
+
**ソースファイル**: `app/java/src/test/java/ch11/ResourceTest.java`
|
|
995
|
+
|
|
996
|
+
```java
|
|
997
|
+
@Test
|
|
998
|
+
@DisplayName("use でリソースを取得して使用")
|
|
999
|
+
void useAcquiresAndReleasesResource() {
|
|
1000
|
+
AtomicBoolean released = new AtomicBoolean(false);
|
|
1001
|
+
|
|
1002
|
+
Resource<String> resource = Resource.make(
|
|
1003
|
+
IO.pure("hello"),
|
|
1004
|
+
s -> released.set(true)
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
String result = resource.use(s -> IO.pure(s.toUpperCase())).unsafeRun();
|
|
1008
|
+
|
|
1009
|
+
assertThat(result).isEqualTo("HELLO");
|
|
1010
|
+
assertThat(released.get()).isTrue();
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
@Test
|
|
1014
|
+
@DisplayName("処理が失敗してもリソースは解放される")
|
|
1015
|
+
void resourceReleasedOnFailure() {
|
|
1016
|
+
AtomicBoolean released = new AtomicBoolean(false);
|
|
1017
|
+
|
|
1018
|
+
Resource<String> resource = Resource.make(
|
|
1019
|
+
IO.pure("hello"),
|
|
1020
|
+
s -> released.set(true)
|
|
1021
|
+
);
|
|
1022
|
+
|
|
1023
|
+
assertThatThrownBy(() ->
|
|
1024
|
+
resource.use(s -> IO.delay(() -> {
|
|
1025
|
+
throw new RuntimeException("error");
|
|
1026
|
+
})).unsafeRun()
|
|
1027
|
+
).isInstanceOf(RuntimeException.class);
|
|
1028
|
+
|
|
1029
|
+
assertThat(released.get()).isTrue();
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
@Test
|
|
1033
|
+
@DisplayName("both で2つのリソースを組み合わせる")
|
|
1034
|
+
void bothCombinesTwoResources() {
|
|
1035
|
+
AtomicBoolean firstReleased = new AtomicBoolean(false);
|
|
1036
|
+
AtomicBoolean secondReleased = new AtomicBoolean(false);
|
|
1037
|
+
|
|
1038
|
+
Resource<Integer> first = Resource.make(
|
|
1039
|
+
IO.pure(1),
|
|
1040
|
+
i -> firstReleased.set(true)
|
|
1041
|
+
);
|
|
1042
|
+
|
|
1043
|
+
Resource<String> second = Resource.make(
|
|
1044
|
+
IO.pure("two"),
|
|
1045
|
+
s -> secondReleased.set(true)
|
|
1046
|
+
);
|
|
1047
|
+
|
|
1048
|
+
var result = Resource.both(first, second)
|
|
1049
|
+
.use(tuple -> IO.pure(tuple._1() + tuple._2()))
|
|
1050
|
+
.unsafeRun();
|
|
1051
|
+
|
|
1052
|
+
assertThat(result).isEqualTo("1two");
|
|
1053
|
+
assertThat(firstReleased.get()).isTrue();
|
|
1054
|
+
assertThat(secondReleased.get()).isTrue();
|
|
1055
|
+
}
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### 12.9 テストピラミッド
|
|
1059
|
+
|
|
1060
|
+
```plantuml
|
|
1061
|
+
@startuml
|
|
1062
|
+
!theme plain
|
|
1063
|
+
|
|
1064
|
+
rectangle "テストピラミッド" {
|
|
1065
|
+
rectangle "E2E テスト\n(少数)" as e2e
|
|
1066
|
+
rectangle "統合テスト\n(中程度)" as integration
|
|
1067
|
+
rectangle "単体テスト + プロパティテスト\n(多数)" as unit
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
e2e -[hidden]down- integration
|
|
1071
|
+
integration -[hidden]down- unit
|
|
1072
|
+
|
|
1073
|
+
note right of unit
|
|
1074
|
+
FP では純粋関数が多いため
|
|
1075
|
+
単体テストが非常に効果的
|
|
1076
|
+
end note
|
|
1077
|
+
|
|
1078
|
+
@enduml
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
---
|
|
1082
|
+
|
|
1083
|
+
## まとめ
|
|
1084
|
+
|
|
1085
|
+
### Part VI で学んだこと
|
|
1086
|
+
|
|
1087
|
+
```plantuml
|
|
1088
|
+
@startuml
|
|
1089
|
+
!theme plain
|
|
1090
|
+
|
|
1091
|
+
rectangle "Part VI: 実践的なアプリケーション" {
|
|
1092
|
+
rectangle "第11章" as ch11 {
|
|
1093
|
+
card "ドメインモデル設計"
|
|
1094
|
+
card "DataAccess 抽象化"
|
|
1095
|
+
card "Resource 管理"
|
|
1096
|
+
card "キャッシュ実装"
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
rectangle "第12章" as ch12 {
|
|
1100
|
+
card "SearchReport"
|
|
1101
|
+
card "TestDataAccess スタブ"
|
|
1102
|
+
card "プロパティベーステスト"
|
|
1103
|
+
card "統合テスト"
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
ch11 --> ch12
|
|
1108
|
+
|
|
1109
|
+
@enduml
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
### キーポイント
|
|
1113
|
+
|
|
1114
|
+
1. **record と sealed interface**: Java 17 の機能で Scala の case class と ADT を表現
|
|
1115
|
+
2. **DataAccess インターフェース**: 外部依存を抽象化してテスト可能に
|
|
1116
|
+
3. **Resource**: acquire/release パターンで安全なリソース管理
|
|
1117
|
+
4. **guarantee**: finally 的なセマンティクスを IO に追加
|
|
1118
|
+
5. **attempt**: 例外を Either に変換してエラーを値として扱う
|
|
1119
|
+
6. **CachedDataAccess**: Ref を使用したスレッドセーフなキャッシュ
|
|
1120
|
+
7. **TestDataAccess**: Builder パターンでテストスタブを柔軟に作成
|
|
1121
|
+
8. **プロパティベーステスト**: @RepeatedTest とランダム生成で不変条件を検証
|
|
1122
|
+
|
|
1123
|
+
### 学習の総括
|
|
1124
|
+
|
|
1125
|
+
```plantuml
|
|
1126
|
+
@startuml
|
|
1127
|
+
!theme plain
|
|
1128
|
+
left to right direction
|
|
1129
|
+
|
|
1130
|
+
rectangle "FP の学習パス" {
|
|
1131
|
+
card "Part I\n基礎" as p1
|
|
1132
|
+
card "Part II\nイミュータブル操作" as p2
|
|
1133
|
+
card "Part III\n型による安全性" as p3
|
|
1134
|
+
card "Part IV\nIO/Stream" as p4
|
|
1135
|
+
card "Part V\n並行処理" as p5
|
|
1136
|
+
card "Part VI\n実践" as p6
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
p1 --> p2
|
|
1140
|
+
p2 --> p3
|
|
1141
|
+
p3 --> p4
|
|
1142
|
+
p4 --> p5
|
|
1143
|
+
p5 --> p6
|
|
1144
|
+
|
|
1145
|
+
@enduml
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
### Scala との比較
|
|
1149
|
+
|
|
1150
|
+
| 概念 | Scala (cats-effect) | Java + Vavr |
|
|
1151
|
+
|------|---------------------|-------------|
|
|
1152
|
+
| 値オブジェクト | `opaque type LocationId` | `record LocationId(String value)` |
|
|
1153
|
+
| ドメインモデル | `case class` | `record` |
|
|
1154
|
+
| ADT | `sealed trait` | `sealed interface` |
|
|
1155
|
+
| リソース管理 | `Resource[IO, A]` | `Resource<A>` (独自実装) |
|
|
1156
|
+
| キャッシュ | `Ref[IO, Map[...]]` | `Ref<Map<...>>` |
|
|
1157
|
+
| エラーハンドリング | `IO[A].attempt` | `IO<A>.attempt()` |
|
|
1158
|
+
| テストスタブ | `val stub = new DataAccess {...}` | `TestDataAccess.builder().build()` |
|
|
1159
|
+
| プロパティテスト | ScalaCheck `Gen[A]` | `@RepeatedTest` + random generators |
|
|
1160
|
+
|
|
1161
|
+
---
|
|
1162
|
+
|
|
1163
|
+
## 演習問題
|
|
1164
|
+
|
|
1165
|
+
### 問題 1: DataAccess の拡張
|
|
1166
|
+
|
|
1167
|
+
以下の要件で `DataAccess` を拡張してください:
|
|
1168
|
+
- 新しいメソッド `findHotelsNearLocation` を追加
|
|
1169
|
+
- 戻り値は `IO<List<Hotel>>`
|
|
1170
|
+
|
|
1171
|
+
<details>
|
|
1172
|
+
<summary>解答</summary>
|
|
1173
|
+
|
|
1174
|
+
```java
|
|
1175
|
+
public record Hotel(String name, double rating, Location location) {}
|
|
1176
|
+
|
|
1177
|
+
public interface DataAccess {
|
|
1178
|
+
// 既存のメソッド...
|
|
1179
|
+
|
|
1180
|
+
IO<List<Hotel>> findHotelsNearLocation(
|
|
1181
|
+
LocationId locationId,
|
|
1182
|
+
int limit
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// TestDataAccess.Builder に追加
|
|
1187
|
+
public Builder withHotels(Hotel... hotels) {
|
|
1188
|
+
this.hotels = List.of(hotels);
|
|
1189
|
+
return this;
|
|
1190
|
+
}
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
</details>
|
|
1194
|
+
|
|
1195
|
+
### 問題 2: プロパティベーステスト
|
|
1196
|
+
|
|
1197
|
+
以下の関数に対するプロパティベーステストを書いてください:
|
|
1198
|
+
|
|
1199
|
+
```java
|
|
1200
|
+
public static List<Location> filterPopularLocations(
|
|
1201
|
+
List<Location> locations,
|
|
1202
|
+
int minPopulation) {
|
|
1203
|
+
return locations.filter(loc -> loc.population() >= minPopulation);
|
|
1204
|
+
}
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
<details>
|
|
1208
|
+
<summary>解答</summary>
|
|
1209
|
+
|
|
1210
|
+
```java
|
|
1211
|
+
static List<Location> randomLocations(int maxCount) {
|
|
1212
|
+
int count = random.nextInt(maxCount + 1);
|
|
1213
|
+
return List.range(0, count).map(i -> randomLocation());
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// プロパティ1: 結果は入力以下の要素数
|
|
1217
|
+
@RepeatedTest(100)
|
|
1218
|
+
@DisplayName("結果のサイズは入力以下")
|
|
1219
|
+
void resultSizeIsLessOrEqual() {
|
|
1220
|
+
List<Location> locations = randomLocations(20);
|
|
1221
|
+
int minPop = nonNegativeInt(10_000_000);
|
|
1222
|
+
|
|
1223
|
+
List<Location> result = filterPopularLocations(locations, minPop);
|
|
1224
|
+
|
|
1225
|
+
assertThat(result.size()).isLessThanOrEqualTo(locations.size());
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// プロパティ2: 結果のすべての要素は条件を満たす
|
|
1229
|
+
@RepeatedTest(100)
|
|
1230
|
+
@DisplayName("すべての結果は最小人口以上")
|
|
1231
|
+
void allResultsMeetMinimum() {
|
|
1232
|
+
List<Location> locations = randomLocations(20);
|
|
1233
|
+
int minPop = nonNegativeInt(10_000_000);
|
|
1234
|
+
|
|
1235
|
+
List<Location> result = filterPopularLocations(locations, minPop);
|
|
1236
|
+
|
|
1237
|
+
assertThat(result.forAll(loc -> loc.population() >= minPop)).isTrue();
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// プロパティ3: 条件を満たす要素はすべて結果に含まれる
|
|
1241
|
+
@RepeatedTest(100)
|
|
1242
|
+
@DisplayName("条件を満たす要素はすべて結果に含まれる")
|
|
1243
|
+
void allQualifyingLocationsAreInResult() {
|
|
1244
|
+
List<Location> locations = randomLocations(20);
|
|
1245
|
+
int minPop = nonNegativeInt(10_000_000);
|
|
1246
|
+
|
|
1247
|
+
List<Location> result = filterPopularLocations(locations, minPop);
|
|
1248
|
+
List<Location> expected = locations.filter(loc -> loc.population() >= minPop);
|
|
1249
|
+
|
|
1250
|
+
assertThat(result).containsExactlyInAnyOrderElementsOf(expected);
|
|
1251
|
+
}
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
</details>
|
|
1255
|
+
|
|
1256
|
+
### 問題 3: Resource の実装
|
|
1257
|
+
|
|
1258
|
+
ファイルを安全に読み取る `Resource` を実装してください。
|
|
1259
|
+
|
|
1260
|
+
<details>
|
|
1261
|
+
<summary>解答</summary>
|
|
1262
|
+
|
|
1263
|
+
```java
|
|
1264
|
+
public static Resource<BufferedReader> fileResource(String path) {
|
|
1265
|
+
return Resource.fromAutoCloseable(
|
|
1266
|
+
IO.delay(() -> new BufferedReader(new FileReader(path)))
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
public static IO<List<String>> readLines(String path) {
|
|
1271
|
+
return fileResource(path).use(reader ->
|
|
1272
|
+
IO.delay(() -> {
|
|
1273
|
+
java.util.List<String> lines = new java.util.ArrayList<>();
|
|
1274
|
+
String line;
|
|
1275
|
+
while ((line = reader.readLine()) != null) {
|
|
1276
|
+
lines.add(line);
|
|
1277
|
+
}
|
|
1278
|
+
return List.ofAll(lines);
|
|
1279
|
+
})
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// 使用例
|
|
1284
|
+
IO<Void> program = readLines("data.txt")
|
|
1285
|
+
.flatMap(lines ->
|
|
1286
|
+
IO.effect(() -> System.out.println("Read " + lines.size() + " lines"))
|
|
1287
|
+
);
|
|
1288
|
+
|
|
1289
|
+
program.unsafeRun();
|
|
1290
|
+
```
|
|
1291
|
+
|
|
1292
|
+
</details>
|
|
1293
|
+
|
|
1294
|
+
---
|
|
1295
|
+
|
|
1296
|
+
## シリーズ全体の総括
|
|
1297
|
+
|
|
1298
|
+
本シリーズでは、「Grokking Functional Programming」の内容に沿って、関数型プログラミングの基礎から実践的なアプリケーション構築までを学びました。
|
|
1299
|
+
|
|
1300
|
+
### 学んだ主な概念
|
|
1301
|
+
|
|
1302
|
+
| Part | 章 | 主な概念 |
|
|
1303
|
+
|------|-----|----------|
|
|
1304
|
+
| I | 1-2 | 純粋関数、参照透過性 |
|
|
1305
|
+
| II | 3-5 | イミュータブルデータ、高階関数、flatMap |
|
|
1306
|
+
| III | 6-7 | Option、Either、ADT |
|
|
1307
|
+
| IV | 8-9 | IO モナド、Stream |
|
|
1308
|
+
| V | 10 | 並行処理、Ref、Fiber |
|
|
1309
|
+
| VI | 11-12 | 実践アプリケーション、テスト |
|
|
1310
|
+
|
|
1311
|
+
### 関数型プログラミングの利点
|
|
1312
|
+
|
|
1313
|
+
1. **予測可能性**: 純粋関数は同じ入力に対して常に同じ出力
|
|
1314
|
+
2. **テスト容易性**: 副作用がないためテストが簡単
|
|
1315
|
+
3. **合成可能性**: 小さな関数を組み合わせて複雑な処理を構築
|
|
1316
|
+
4. **並行安全性**: イミュータブルデータは競合状態を防ぐ
|
|
1317
|
+
5. **型安全性**: Option、Either で null や例外を型で表現
|
|
1318
|
+
|
|
1319
|
+
### 次のステップ
|
|
1320
|
+
|
|
1321
|
+
- Vavr のより高度な機能を学ぶ
|
|
1322
|
+
- プロジェクト Reactor や RxJava を探索
|
|
1323
|
+
- Virtual Thread と組み合わせた実践的な並行処理
|
|
1324
|
+
- 実際のプロジェクトで FP を適用する
|