Functional Calisthenics in Kotlin: Kotlinで「関数型エクササイズ」を実践しよう

KentOhashi 0 views 54 slides Oct 31, 2025
Slide 1
Slide 1 of 54
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54

About This Presentation

関数型プログラミングのコード設計に親しむために、Kotlinで「関数型エクササイズ」しよう💪

Kotlin Fest 2025セッション概要: https://2025.kotlinfest.dev/timetable/1719040800_b/


Slide Content

Functional Calisthenics
in Kotlin
Kotlinで「関数型エクササイズ」を実践しよう
#KotlinFest
1

のシニアエンジ ニア
スタート アップと投資家のや り取りを効率化する
データ管理プラット フォー ムを開発し ている
技術スタック : Kotlin/Ktor & TypeScript/Vue.js
の運営にも協力
, などの関数型言 語の愛好者
の運営スタッ フ(座長のひとり )
Java, , Clojure, KotlinとJVM言語での開発経験
Kotlinの実務利用は 1年半ほど ??????
lagénorhynque??????カマイルカ
株式会社スマ ートラウンド
Server-Side Kotlin Meetup
ClojureHaskell
関数型まつり
Scala
2

1. オブジェクト指向エ クササイズ
2. 関数型プログラミングというスタイ ル
3. ??????の「関数型エ クササイズ」
4. Kotlinでの実践例
3

1. オブジェクト指向エクササイズ
4

O'Reilly Japanの より
『ThoughtWorksアンソロジー』
書籍詳細ペ ージ
5

オブジェクト指向エクササイズ
『ThoughtWorksアンソロジー』第 5章のタイト ル
原題: Object Calisthenics (≒ オブジェクト体操 )
手続き型プログラミングからオブジ ェクト指向プロ
グラミングのコード設計の発想に親しむための訓練
方法とし て(少々大胆で今や古 めかしい?)ルール集
i.e. パラダイムシフトに順応し てもらうきっかけ
→ 関数型プログラミングについ ても同じような
アプローチを考えたい ??????
6

オブジェクト指向エクササイズ 9つのルール
ルール1: 1つのメソッドにつき インデントは 1段階
までにすること
主な狙い : 責務の分離
ルール2: else句を使用しない こと
主な狙い : 可読性
ルール3: すべてのプリミテ ィブ型と文字列型をラッ
プすること
主な狙い : カプセル化、型安全性
7

ルール4: 1行につきドットは 1つまでにすること
主な狙い : 責務の分離、カプセル化
ルール5: 名前を省略しないこ と
主な狙い : 可読性、責務の分離
ルール6: すべてのエンティティを小さくすること
主な狙い : 責務の分離、カプセル化
8

ルール7: 1つのクラスにつきイ ンスタンス変数は 2
つまでにすること
主な狙い : 責務の分離、カプセル化
ルール8: ファー ストクラスコレクションを使用する
こと
主な狙い : カプセル化
ルール9: Getter、Setter、プロパティを使用しない
こと
主な狙い : カプセル化
9

ルール1: 1つのメソッドにつきインデントは1段階までに
すること
リファクタリング前 :
class Board {
fun board(): String =
buildString {
for (row in data) {
for (square in row)
append(square)
appendLine()
}
}
}
10

リファクタリング後 :
class Board {
fun board(): String =
buildString {
collectRows( this)
}
fun collectRows(sb: StringBuilder) { // 拡張関数にする案も
for (row in data)
collectRow(sb, row)
}
fun collectRow(sb: StringBuilder, row: List<Square>) {
for (square in row)
sb.append(square)
sb.appendLine()
}
}
11

ルール2: else句を使用しないこと
リファクタリング前 :
リファクタリング後 :
fun endMe() {
if (status == DONE) {
doSomething()
} else {
doSomethingElse()
}
}
fun endMe() {
if (status == DONE) {
doSomething()
return
}
doSomethingElse()
}
12

リファクタリング前 :
リファクタリング後 :
fun head(): Node {
if (isAdvancing())
return first
else
return last
}
fun head(): Node =
if (isAdvancing()) first else last
13

ルール4: 1行につきドットは1つまでにすること
リファクタリング前 :
class Board {
class Piece(..., val representation: String)
class Location(..., val current: Piece)
fun boardRepresentation(): String =
buildString {
for (l in squares())
append(l.current.representation.substring( 0,
1))
}
}
14

リファクタリング後 :
class Board {
class Piece(..., private val representation: String) {
fun character(): String =
representation.substring( 0, 1)
fun addTo(sb: StringBuilder) {
sb.append(character())
}
}
class Location(..., private val current: Piece) {
fun addTo(sb: StringBuilder) {
current.addTo(sb)
}
}
// 次ページに続く
15

// 前ページから続く
fun boardRepresentation(): String =
buildString {
for (l in squares())
l.addTo( this)
}
}
16

ルール7: 1つのクラスにつきインスタンス変数は2つま
でにすること
リファクタリング前 :
class Name(
val first: String,
val middle: String,
val last: String,
)
17

リファクタリング後 :
class Name(
val family: Surname,
val given: GivenNames,
)
class Surname(val family: String)
class GivenNames(val names: List<String>)
18

2. 関数型プログラミングという
スタイル
19

[参考] での??????の発表関数型まつり2025
関数型言語テイステ ィング: Haskell, Scala, Clojure, Elixirを比べて味わう関数型プログラミングの旨さ
20

(現在の)??????によるFPとFPLの定義
関数型プログラミング := 純粋関数を基本要素とし
て、その組み合わせによっ てプログラ ムを構成し て
いくプログラミングスタイ ル
→ 言語を問わ ず実践可能 (実践しやすさは異なる )
関数型言語 := 関数型プログラミングが言語 /標準ラ
イブラリレベルで十分に支援される (そして関数型
プログラミングスタイ ルがユビキタスな )言語
→ 例えば JavaScript/TypeScriptやJava、Kotlin、
古典的な Lisp方言は含めない
21

重視する
もの
(values)
⾔語
(languages)
処理系 /実装
(implementations)
パター ン
(patterns)
Lisp
式指向
(expression-oriented)
不変性
(immutability)
宣⾔型プログラミング
(declarative programming)
(型)安全性
((type) safety)
合成可能性
(composability)
永続性
(persistence)
純粋性
(purity)
Clojure
Erlang
Elixir
Haskell
OCaml
Standard ML
ML
適⽤
(apply)
評価
(eval)
抽象構⽂⽊
(abstract syntax tree; AST)
マクロ
(macro)
参照透過性
(referential transparency)
再帰
(recursion)
遅延評価
(lazy evaluation)
メモ化
(memoization)
並⾏プログラミング
(concurrent programming)
アクターモデ ル
(actor model)
STM
(software transactional
 memory)
CSP
(communicating sequential
 processes)
継続
(continuation)
⾼階関数
(higher-order function)
関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
関数型
プログラミング
(functional
programming)
理論
(theories)
数学
(mathematics)
圏論
(category theory)
型システム
(type system)
ラムダ計算
(lambda calculus)
メタプログラミング
(metaprogramming)
形式⼿法
(formal methods)
定理証明⽀援系
(theorem prover)
評価
(evaluation)
制御
(control)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェー ン
(method chaining)
代数的データ型
(algebraic data type; ADT)
パター ンマッチ
(pattern matching)
クロージ ャー/関数閉包
(closure)
オブジェクト
(object)
Idris
Elm
パーサー コンビネーター
(parser combinator)
離散数学
(discrete mathematics)
契約プログラミング
(contract programming)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
篩型
(refinement type)
依存型
(dependent type)
カプセル化
(encapsulation)
ポリモー フィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
カリー =ハワード同型対応
(Curry–Howard
 correspondence)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
 data structure)
永続データ構造
(persistent data structure)
Coq (Rocq)
Scala
構⽂解析
(parse)
オーバーロード /多重定義
(overloading)
命令型プログラミング
(imperative programming)
⽂指向
(statement-oriented)
副作⽤
(side effect)
破壊的更新
(mutation)
可変性
(mutability)
pipes & filters
goroutines & channels
プロパティベーステスト
(property-based testing)
⼊出⼒
(I/O)
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リ スト/ストリー ム
(lazy list/stream)
同図像性
(homoiconicity)
超循環評価器
(meta-circular evaluator)
セルフホスティング
(self-hosting)
ベクター
(vector)
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
 programming)
アプリカテ ィブ
(applicative)
トレイト
(trait)
Agda
必要呼び
(call by need)
F#
ジェネリクス
(generics)
Lean
Gleam
末尾再帰
(tail recursion)
意味論
(semantics)
型推論
(type inference)
再帰型
(recursive type)
GoFデザインパター ン
(GoF Design Patterns)
※ ??????が思い浮かぶ概念 /用語を連想的に列挙したもの (網羅的でも体系的でもない )
??????の「関数型プログラミング」コンセプトマップ
22

重視する
もの
(values)
式指向
(expression-oriented)
不変性
(immutability)
宣⾔型プログラミング
(declarative programming)
(型)安全性
((type) safety)
合成可能性
(composability)
永続性
(persistence)
純粋性
(purity)
参照透過性
(referential transparency)
命令型プログラミング
(imperative programming)
⽂指向
(statement-oriented)
副作⽤
(side effect)
破壊的更新
(mutation)
可変性
(mutability)
⼊出⼒
(I/O)
23

関数型プログラミングで重要な性質
純粋性 (purity)
不変性 (immutability)
合成可能性 (composability)
式指向 (expression-oriented)
宣言型プログラミング (declarative programming)
(型)安全性 ((type) safety)
24

3. ??????の「関数型エクササイズ」
関数型プログラミングのコード設計に親しむために
25

??????の「関数型エクササイズ」 9つのルール
ルール1: 1つの関数は単一の (文ではなく )式で表す
こと
主な狙い : 式指向
ルール2: 関数は引数と戻り値 を持つこと
主な狙い : 純粋性
ルール3: 関数は引数以外の入力に依存しないこと
主な狙い : 純粋性
26

ルール4: I/O処理は関数とし て分離し注入すること
主な狙い : 純粋性
ルール5: 再代入可能な変数、 可変なデータ構造を
使用/定義しないこと
主な狙い : 不変性
ルール6: 繰り返し処理はルー プ構文ではなくコレク
ション操作で行うこと
主な狙い : 宣言型プログラミング
27

ルール7: 汎用的な構文や関数 よりも目的に特化し
た関数を選択すること
主な狙い : 宣言型プログラミング
ルール8: 既存の関数を部分適用/合成し て新たな関
数を定義すること
主な狙い : 合成可能性
ルール9: 不正な状態が表せな いようにデータ型の
選択/定義で制限すること
主な狙い : (型)安全性
28

4. Kotlinでの実践例
29

今回採用した方針
Kotlinの基本的な言語機能を活か す
Kotlinに無理なく馴染む表現を目指す
オブジェクト指向スタイ ルを排除せず併用する
Kotlinはオブジェクト指向言語
準標準 /サード パーティライブラリに依存しない
??????< 例えば の便利な要素を利用するのも良
さそうだが、全面的に使いたくなったらむしろ
が適し ていそう ??????(Kotlinでそこまでする ?)
Arrow
Scala
30

ルール1: 1つの関数は単一の(文ではなく)式で表すこと
リファクタリング前 :
リファクタリング後 :
fun endMe() {
if (status == DONE) {
doSomething()
return
}
doSomethingElse()
}
fun endMe() =
if (status == DONE) doSomething()
else doSomethingElse()
31

文ではなく式とし て表すこと で命令型のコードが排
除されやすくなる
Kotlinでは:
命令型言語でお馴染み (?)の構文を引き継ぎ つつも
は式になっ ていて扱いやすい
の構文を積極的に
活用すると良い制約になる
1つの式で表しづらくなったら分割すること を
強いられる
分岐構文
単一式 (single-expression)関数
32

ルール2: 関数は引数と戻り値を持つこと
リファクタリング前 :
リファクタリング後 :
fun endMe() =
if (status == DONE) doSomething()
else doSomethingElse()
fun endMe(input: SomeInput): SomeOutput =
if (status == DONE) doSomething(input)
else doSomethingElse(input)
33

引数をとらない /戻り値を返さない関数は副作用を
持ちやすいので必要最小限にする
Kotlinでは:
オブジェクト指向言語でのク ラスに属する関数
(メソッド )のレシー バーは暗黙 的な引数といえ る
クラスとしてモデル化するなら、明示的な引数の
ない関数もありうる
ただし、クラ スで表す理由は自問したい
34

ルール3: 関数は引数以外の入力に依存しないこと
リファクタリング前 :
リファクタリング後 :
var n: Int = 42 // 関数外の不安定な変数 /値
fun f(x: Int): Int = x + n
fun f(x: Int, y: Int): Int = x + y
// 適宜、インター フェー スを整え る
fun g(x: Int): Int = f(x, 42)
fun h(x: Int, y: Int = 42): Int = f(x, y)
35

引数を介さず関数外からの入力 (グロー バル/モジュ
ール/クラス変数など )にアクセスすると関数の参照
透過性が損なわれ やすいので 避ける
不変の値 (定数)を参照するのであれば問題はない
(関数型言語でもクロージ ャーはありふれ ている)
Kotlinでは:
厳格に従うと、クラ スのメソッドが他のメン バー
変数にア クセスすることさえ できなくなる
プライベートメソッドでは引 数を介したア クセス
のみに制限するような規約も考えられる
36

ルール4: I/O処理は関数として分離し注入すること
リファクタリング前 :
リファクタリング後 :
fun listUsers(ids: List<UserId>): List<UserView> =
UserRepository()
.findByIds(ids)
.map { UserView(it) }
fun listUsers(
ids: List<UserId>,
resolveUsers: (List<UserId>) -> List<User>,
): List<UserView> =
resolveUsers(ids).map { UserView(it) }
// 利用例
listUsers(userIds) { ids ->
UserRepository().findByIds(ids)
}
37

純粋関数を基本ブ ロックとす るため、 I/O処理など
副作用の発生箇所は分離 /局所化したい
高階関数によっ て注入するアプローチがシンプ ル
かつ汎用的
I/Oを型レベルで分離できる言語 /ライブラリも
Kotlinでは:
interface や abstract class を利用し ても
よいが、 で十分な状況も多々ありそう
インター フェー スを最小化する ことにも繋がる
高階関数
38

ルール5: 再代入可能な変数、可変なデータ構造を使用/
定義しないこと
リファクタリング前 :
リファクタリング後 :
val wordCount = mutableMapOf<String, Int>()
words.forEach { word ->
val count = wordCount.getOrDefault(word, 0)
wordCount[word] = count + 1
}
// wordCount.toMap() で読み取り専用マッ プは得られる
val wordCount: Map<String, Int> =
words.groupingBy { it }.eachCount()
39

関数型言語では再代入可能な変数がなく可変データ
構造が定義 /利用しに くくなっていることも多い
不変だが効率的なコレクション実装もある
関数/モジュールに閉じて可変な変数 /データ構造
を扱うのは問題ない (パフォー マンスの都合など )
Kotlinでは:
変数は val で宣言し、 は読み
取り専用なものを使う (今や一般的かも ?)
明示的に可変な変数やデータ構造を扱わ ずに済む
関数を選択 /設計する
標準コレクション
40

ルール6: 繰り返し処理はループ構文ではなくコレクシ
ョン操作で行うこと
リファクタリング前 :
for (n in 1..100) {
when {
n % 15 == 0 -> println("FizzBuzz")
n % 3 == 0 -> println("Fizz")
n % 5 == 0 -> println("Buzz")
else -> println(n)
}
}
41

リファクタリング後 :
fun fizzBuzz(n: Int): String =
when {
n % 15 == 0 -> "FizzBuzz"
n % 3 == 0 -> "Fizz"
n % 5 == 0 -> "Buzz"
else -> n.toString()
}
(1..100)
.map(::fizzBuzz)
.forEach(::println)
42

(イミュータブ ル)コレクション の操作 (変換)は関数
型プログラミングのありふれた日常の一部
プログラムとはデータ変換の連鎖
効率のために命令型のルー プ構文を局所的に利用
することはありうる
Kotlinでは:
標準ライブラリに高レベルな関数が充実し ている
ので活用する
for, while や forEach 関数は I/Oなどの副作用
発生を意図する状況以外では利用しない
43

ルール7: 汎用的な構文や関数よりも目的に特化した関
数を選択すること
リファクタリング前 :
リファクタリング後 :
val numberOfAdultUsers =
users.fold(0) { acc, user ->
if (user.age >= 18) acc + 1 else acc
}
val numberOfAdultUsers =
users.count { it.age >= 18 }
44

より宣言的になるように意図が表れる形式を選ぶ
汎用構文 < 汎用関数 < 目的特化関数
コレクションに対し て:
e.g. loop, match < fold < map, filter, sum
直和型に対し て:
e.g. match < fold < map, filter
Kotlinでは:
様々な用途の関数を知っ て使い分け る、自ら定義
する
45

ルール8: 既存の関数を部分適用/合成して新たな関数を
定義すること
リファクタリング前 :
fun filterUsersByTargetAge (users: List<User>, minAge: Int):
List<User> =
users.filter { it.age >= minAge }
fun <K : Comparable<K>> sortUsers(users: List<User>, keyFn:
(User) -> K): List<User> =
users.sortedBy(keyFn)
fun takeFirstUsers(users: List<User>, n: Int): List<User> =
users.take(5)
// 上記の関数がある状況で
fun listFirst5AdultUsers (users: List<User>): List<User> =
takeFirstUsers(
sortUsers(
filterUsersByTargetAge(users, 18)
) { it.joinedAt }, 5
)
46

リファクタリング後 (1):
リファクタリング後 (2):
fun listFirst5AdultUsers (users: List<User>): List<User> =
filterUsersByTargetAge(users, 18)
.let { sortUsers(it) { it.joinedAt } }
.let { takeFirstUsers(it, 5) }
fun listFirst5AdultUsers (users: List<User>): List<User> =
users
.filterAdult( 18)
.sortByJoinedAt()
.takeFirst5()
private fun List<User>.filterAdult(minAge: Int): List<User> =
filterUsersByTargetAge( this, minAge)
private fun List<User>.sortByJoinedAt(): List<User> =
sortUsers(this) { it.joinedAt }
private fun List<User>.takeFirst5(): List<User> =
takeFirstUsers(this, 5)
47

関数を簡潔に再利用するために部分適用や関数合成
に役立つユ ーティリティを活用する
多くの関数型言語にはラ ムダ式の略記法、パ イプ
演算子、自動的なカリー化などがある
Kotlinでは:
部分適用や関数合成を楽にする仕組みはなさそう
メソッドチェー ンも関数合成 の一種とみなせ る
let などの が便利
や を活用
して滑らかに繋ぐことも できる
スコー プ関数
拡張関数 レシー バー付き関数リテラ ル
48

ルール9: 不正な状態が表せないようにデータ型の選択/
定義で制限すること
リファクタリング前 :
data class User(
val id: UserId,
val isRegistered: Boolean,
val isActive: Boolean,
val joinedAt: LocalDateTime?,
val leftAt: LocalDateTime?,
) {
companion object {
fun registeringUser(id: UserId): User =
User(id, false, false, null, null)
fun activeUser(id: UserId, /* 略 */): User =
User(id, true, true, joinedAt, null)
fun inactiveUser(id: UserId, /* 略 */): User =
User(id, true, false, joinedAt, leftAt)
}
}
49

リファクタリング後 :
sealed interface User {
val id: UserId
data class RegisteringUser(
override val id: UserId,
) : User
data class ActiveUser(
override val id: UserId,
val joinedAt: LocalDateTime,
) : User
data class InactiveUser(
override val id: UserId,
val joinedAt: LocalDateTime,
val leftAt: LocalDateTime,
) : User
}
50

代数的データ型でとりうる値のパター ンを定義し、
パター ンマッチングで網羅的 に分岐 /分解する
booleanやoptional/nullableの乱用を避け る
組み合わせで不正な状態が生じやすくなるため
Kotlinでは:
sealed interface/class や enum で代数的
データ型を表せ る
when 式で網羅的に場合分けできる
??????< パター ンマッチしたい (Javaではできる )
51

おわりに
??????が考え る、関 数型プログラミング実践者の発想 :
⛓️ 適切な制約が解放をもたら す
→ 純粋関数と不変データを基本に
→ 不正値を表現不能にし てより(型)安全に
?????? 単純で安定なブ ロックを基礎 に全体を構成する
→ 式指向に、宣言的に、合成可能に
52

Kotlinらしく関数型プログラミングを実践しよう ??????
設計改善の機会になるはず ??????
(もの足りなくなったら (?)、本格的な関数型言語もぜひ ??????)
53

Further Reading
5章 オブジェクト指向エ クササイズ
原書:
原書:
『ThoughtWorksアンソロジー』
関数型言語テイステ ィング: Haskell, Scala, Clojure,
Elixirを比べて味わう関数型プログラミングの旨さ
『なっとく!関数型プログラミング』
Grokking Functional Programming
『関数型ド メインモデリング 』
Domain Modeling Made Functional
54