Slide 1

Slide 1 text

Kanon #jjug_ccc Java で学ぶ 代数的データ型 ysknsid25 ysknsid25.bsky.social

Slide 2

Slide 2 text

⛳ このセッションのゴール ⛳ 2 ● 代数的データ型とは何かを理解できる ● 代数的データ型をJavaで表現することができるようになる

Slide 3

Slide 3 text

関数型プログラミングといえば? な キーワードを 1つ想像してください 3

Slide 4

Slide 4 text

4 あなたが想像したのはこれですね? 純粋関数

Slide 5

Slide 5 text

5 純粋関数とは ● 戻り値は常に一つ ● 関数は引数にのみ基づいて戻り値を計算する ● 関数は既存の値を変更しない

Slide 6

Slide 6 text

6 ではこういう純粋関数はどうだろう? public static String activityStatus(Date latestLoginDate, Date referenceDate) { if (latestLoginDate == null) { return "INACTIVE"; } Calendar calendar = Calendar.getInstance(); calendar.setTime(referenceDate); calendar.add(Calendar.YEAR, -1); Date oneYearAgo = calendar.getTime(); return latestLoginDate.after(oneYearAgo) ? "ACTIVE" : "INACTIVE"; } 戻り値は1つの文字列で 引数にのみ基づいてるし 既存の値は変えてない 例なので結構極端 最終ログインが 1年前ならINACTIVE、1年以内ならActiveとする

Slide 7

Slide 7 text

7 先の関数を呼び出す public static void main (String[] args) { // do something… String status = activityStatus(latestLoginDate, referenceDate) // do something… } INACTIVE ACTIVE 実装を見ないと 振る舞いがわからない 引数はこの組み合わせ意味 (活動中かどうかの条件 )を 持っているが それは実装を見ないとわから ない

Slide 8

Slide 8 text

8 純粋関数はなんのため? 関数の宣言を見て振る舞いが想像できる & 期待どおりに振る舞う 代数的データ型が役立つ!!

Slide 9

Slide 9 text

代数的データ型 9

Slide 10

Slide 10 text

代数的データ型 = 直積型 & 直和型 10 また知らないワード…

Slide 11

Slide 11 text

11 直積型 代数的データ型 ● ここでは 星座 × 年齢。A × B × C … 掛け算なので直積 ● JavaでいうとRecordやBean Class ● 要はデータクラス 年齢 星座 (蟹, 20) (双子, 30) (射手, 40) (山羊, 20) public record PersonInfo( String constellation, int age ){}

Slide 12

Slide 12 text

12 直和型 代数的データ型 ● ここでは 星座 は必ず12個で、牡牛座であり牡牛座であることはありえない。 ● 和集合 A ∪ B ただし A ∩ B = φ … 直和 ● Javaでいうとenumやsealed class 星座 public enum Constellation { ARIES, // おひつじ座 TAURUS, // おうし座 GEMINI, // ふたご座 CANCER, // かに座 LEO, // しし座 VIRGO, // おとめ座 LIBRA, // てんびん座 SCORPIO, // さそり座 SAGITTARIUS, // いて座 CAPRICORN, // やぎ座 AQUARIUS, // みずがめ座 PISCES // うお座 } 牡羊 牡牛 双子 蟹 乙女 天秤 蠍 射手 山羊 水瓶 魚 獅子

Slide 13

Slide 13 text

代数的データ型 = 直積型 & 直和型 13 改めて

Slide 14

Slide 14 text

代数的データ型が ないとき 14

Slide 15

Slide 15 text

15 代数的データ型になっていないコード 代数的データ型 import java.util.Optional; public record PeriodInYears(int start, Optional end){}; public static void main(String[] args) { PeriodInYears p1 = new PeriodInYears(1981, Optional.empty()); PeriodInYears p2 = new PeriodInYears(1968, Optional.of(1980)); System.out.println(p1); // PeriodInYears[start=1981, end=Optional.empty] System.out.println(p2); // PeriodInYears[start=1968, end=Optional[1980]] } 活動期間を表現したい endが存在しない = 活動中 endが存在する = 活動終了 と表現できないだろうか? 活動中なのに活動終了と判定され る間違いも起きそう 直積の概念のみが反映された状態のコード

Slide 16

Slide 16 text

16 代数的データ型が あるとき

Slide 17

Slide 17 text

17 代数的データ型になったコード 代数的データ型 // 直和型 public sealed interface YearsActive permits StillActive, ActiveBetween {} // record(直積)×sealed(直和)の代数的データ型 public record StillActive(int since) implements YearsActive { /** 妥当性の検証は省略 */ } public record ActiveBetween(int start, int end) implements YearsActive { /** 妥当性の検証は省略 */ } public static void main(String[] args) { YearsActive y1 = new StillActive(2005); YearsActive y2 = new ActiveBetween(1990, 2000); System.out.println(y1); // StillActive[since=2005] System.out.println(y2); // ActiveBetween[start=1990, end=2000] } データの意味が一目でわかるようになったし、間違いも起こりにくそう

Slide 18

Slide 18 text

18 sealedではなくenumじゃだめなの? 代数的データ型 public enum YearsActive { STILL_ACTIVE { private int since; @Override void setData(int a, int b) { this.since = a; } }, ACTIVE_BETWEEN { private int start; private int end; @Override void setData(int a, int b) { this.start = a; this.end = b; } }; abstract void setData(int a, int b); } enumで直積の性質を表現するのは無理がある。 以下のようにだいぶ無理があるコードができあがる。int b を無理やり入れないとだし、どう考えてもスマートじゃない public static void main(String[] args) { YearsActive y1 = YearsActive.STILL_ACTIVE; y1.setData(2005, 9999); // 9999ってなんだ・・・ YearsActive y2 = YearsActive.ACTIVE_BETWEEN; y2.setData(1990,2000); System.out.println(y1); // STILL_ACTIVE System.out.println(y2); // ACTIVE_BETWEEN }

Slide 19

Slide 19 text

19 継承じゃだめなの? 代数的データ型 sealedではなく継承を使ってしまうと、理論上無限の集合が出来上がる = 直和の性質がない YearsActive StillActive ActiveBetween PreviousLife 知らないところで勝手に前世を定義できてしまう もちろん来世も定義できる

Slide 20

Slide 20 text

20 さらなる恩恵を受ける 代数的データ型 // 直和型 public sealed interface YearsActive permits StillActive, ActiveBetween {} // record(直積)×sealed(直和)の代数的データ型 public record StillActive(int since) implements YearsActive { /** 妥当性の検証は省略 */ } public record ActiveBetween(int start, int end) implements YearsActive { /** 妥当性の検証は省略 */ } public static void main(String[] args) { YearsActive y1 = new StillActive(2005); YearsActive y2 = new ActiveBetween(1990, 2000); System.out.println(y1); // StillActiveならsinceだけ出力 System.out.println(y2); // ActiveBetweenは開始と終了を出力 } System.out.println()の内容をデータによって切り分ける関数を作るとする

Slide 21

Slide 21 text

21 代数的データ型の威力 代数的データ型 public sealed interface YearsActive permits StillActive, ActiveBetween {} public record StillActive(int since) implements YearsActive {} public record ActiveBetween(int start, int end) implements YearsActive {} public static void main(String[] args) { YearsActive y1 = new StillActive(2005); YearsActive y2 = new ActiveBetween(1990, 2000); System.out.println(getYearsMessage(y1)); System.out.println(getYearsMessage(y2)); } static String getYearsMessage(YearsActive ya) { return switch (ya) { case StillActive sa -> "Still active since " + sa.since(); case ActiveBetween ab -> "Active between " + ab.start() + " and " + ab.end(); }; } ● シグネチャから処理が明らかになった ● 引数として必要なデータが何か?も明らか ● データの不整合が起こり得ない ○ 先のenumでみた9999とか ● YearsActiveでとりうる値の範囲が限定され ることで、switchにデフォルトが生えない ○ コンパイル時点でデータの整合性が担 保される ● 逆にYearsActiveに新たな値が増えた場合 は、getYearsMessage でエラーが出るので修 正漏れが出ない

Slide 22

Slide 22 text

22 まとめ ● 代数的データ型 = 直積型 & 直和型 ○ enumは直積の性質がない ○ 継承は直和の性質がない ● 代数的データ型を使うことでより明示的なシグネチャを定義できる ● 代数的データ型を使うことで取りうるデータの範囲を型で表現できる ● 代数的データ型はコンパイル時点での品質保証に寄与する

Slide 23

Slide 23 text

Happy Hacking !! 水瀬いのり さんが推し の @ysknsid25 @ysknsid25.bsky.social Presented by Kanon でした

Slide 24

Slide 24 text

24 ● これまでの`YearsActive`データモデルを実際に定義しましょう ● `master`ブランチにリファクタ前のコードがあります ● `answer`ブランチはリファクタ後のコードです。さっきまで出していたサン プルと同じ内容になっています ● 今学んだ代数的データ型を定義し、`master`ブランチのテストコードが通 るように修正してみてください 実際に代数的データ型を書いてみよう! 時間が余れば https://github.com/ysknsid25/jjug-spring-2025