狠狠撸

狠狠撸Share a Scribd company logo
Kotlin Meets Data-Oriented
Programming
Kotlinで実践する「データ指向プログラミング」
1
のシニアエンジニア
主要技術スタック
のたまごスポンサー
の運営企業
関数型?語 関数型プログラミングが?好き
仕事や趣味で などに?く
触れてきた
現職で初めて を仕事で読み書きするように
なって半年ほど
lagénorhynque カマイルカ
株式会社スマートラウンド
2
Kotlin Fest 2024への私 のCfP
3
書籍 は、プログラミン
グ?語 において典型的なプログラミングスタ
イルの根幹にある考え?を他?語でも応?できる形で
抽出し紹介する試みであるということができます。
を実務や趣味で継続的に利?するとともに?
較的最近 に再??した?場から、この本で提?
されている「データ指向プログラミング」というプロ
グラミングスタイルを概説しながら らしい実践
の可能性について考察します。
Kotlin Meets Data-Oriented Programming:
Kotlinで実践する「データ指向プログラミング」
『データ指向プログラミング』
4
データ指向プログラミングとは
への適?可能性を探る
まとめ
5
1. データ指向プログラミングとは
6
書籍 まえがき
『データ指向プログラミング』
特別なのは機能ではなく原則だと
いうことで意?が?致した。
の基本原則を抜き出そうとしていた
私たちは、実際には、それらの原則
を他のプログラミング?語に応?で
きることに気づいた。本書の構想が
沸いてきたのはそのときだった。私
が でとても気に?っている点
を世界中の開発者コミュニティに伝
えたかった。
7
に魅了された著者が ?語 コミュニテ
ィで?般的なプログラミングスタイルのエッセンス
を他?語でも応?できる形で抽出しようとした本
古典的な に対するアンチテーゼといえる
他の?語や設計思想と必ずしも馴染まず批判され
ることもある 批判も理解できる
である の視点から捉え直し、 での
現実的な応?の可能性を考えたい
8
(ちなみに) Clojureとは
動的型付き ?オブジェクト指向
年に登場した
年?
年?
作者 のプレゼン は他
のコミュニティでも多少知られているかも
?語の設計にも?濃く反映されている、
の重要性について語っている
関数型?語
古典的な には当初から批判的
モダンに再設計された 系?語
?語
9
データ指向プログラミングの背景
古典的な のアプローチに対する問題意識
必要以上の複雑さを?みがち
いろいろな要素が絡み合っている
硬直的で柔軟性に?けることがある
フレームワークに頼らざるを得なかったり
→ もっとシンプル に情報を扱うアプローチ
があるはず
そうして?まれたのが ?語でもある
10
データ指向プログラミングの原則
原則 コードをデータから切り離す
原則 データを汎?的なデータ構造で表す
原則 データはイミュータブルである
原則 データスキーマをデータ表現から切り離す
のプログラミングスタイルそのもの
11
データ指向プログラミングと親和性の?い技術
ロックフリー な楽観的並?性制御
の
純粋な関数、不変なデータとの相性が良い
状態変化のタイムトラベル、リプレイ
状態が不変の汎?データ構造で表現されていれば
極めて簡単
永続データ構造
不変 かつ永続的 であれば
効率も犠牲になりにくい
『純粋関数型データ構造』
12
2. Kotlinへの適?可能性を探る
13
データ指向プログラミングの原則(再掲)
原則 コードをデータから切り離す
原則 データを汎?的なデータ構造で表す
原則 データはイミュータブルである
原則 データスキーマをデータ表現から切り離す
のプログラミングスタイルそのもの
14
原則 #1: コードをデータから切り離す
15
Clojureの場合
関数の定義 データはマップ { } で表す
(ns dop-examples) ; 名前空間(namespace)の定義
(defn make-author [first-name last-name num-of-books]
{:first-name first-name
:last-name last-name
:num-of-books num-of-books})
(defn full-name [{:keys [first-name last-name]}]
(str first-name " " last-name))
(defn prolific? [{:keys [num-of-books]}]
(or (some-> num-of-books (> 100))
false))
16
利?例
;; FYI: プロンプトの `dop-examples` は現在の名前空間(モジュール)
;; そこでdef/requireされているものは?修飾名で参照できる
dop-examples> (let [data (make-author "Isaac" "Asimov" 500)]
(full-name data))
"Isaac Asimov"
17
「レコード」 ≒ を定義することもできる
マップとレコードはインターフェースが共通している
ため、関数 full-name はそのまま使える
(defrecord Author
[first-name
last-name
num-of-books])
(defn make-author' [first-name last-name num-of-books]
(->Author first-name last-name num-of-books))
dop-examples> (let [data (make-author' "Isaac" "Asimov" 500)]
(full-name data))
"Isaac Asimov"
18
(書籍より) 利点とコスト
主な利点
コードをさまざまなコンテキストで再利?できる
コードを単体でテストできる
システムがあまり複雑にならない傾向にある
主なコスト
どのコードがどのデータにアクセスできるのかを
制御できない
パッケージ化がない
システムを構成するエンティティの数が増える
19
Kotlinの場合
データと関数の定義
クラス オブジェクトは「モジュール」でもある
data class Author(
val firstName: String,
val lastName: String,
val numOfBooks: Int?,
)
object NameCalculation {
fun fullName(data: Author): String =
"${data.firstName} ${data.lastName}"
}
object AuthorRating {
fun isProlific(data: Author): Boolean =
data.numOfBooks?.let { it > 100 } ?: false
}
20
利?例
らしい ドット記法が必要であれば
> val data = Author("Isaac", "Asimov", 500)
> NameCalculation.fullName(data)
res2: kotlin.String = Isaac Asimov
> fun Author.fullName(): String =
NameCalculation.fullName(this)
> data.fullName()
res4: kotlin.String = Isaac Asimov
21
や
の代わりに
インターフェースを定義することで特定の具象型
Author2 に縛られなくすることはできる
構造的型 拡張可能レコード
interface Namable {
val firstName: String
val lastName: String
}
data class Author2(
override val firstName: String,
override val lastName: String,
val numOfBooks: Int?,
) : Namable
object NameCalculation2 {
fun fullName(data: Namable): String =
"${data.firstName} ${data.lastName}"
}
原則 #2: データを汎?的なデータ構造で表す
23
Clojureの場合
連想データに対するあらゆるオペレータ 関数 マクロ
特殊形式 が利?できる
;; マップ(リテラルで作成)
dop-examples> {:first-name "Isaac"
:last-name "Asimov"
:num-of-books 500}
{:first-name "Isaac", :last-name "Asimov", :num-of-books 500}
;; レコード(コンストラクタ関数で作成)
dop-examples> (->Author "Isaac" "Asimov" 500)
{:first-name "Isaac", :last-name "Asimov", :num-of-books 500}
;; どちらも Associative (連想データ)インターフェースを実装している
dop-examples> (associative? {:first-name "Isaac"
:last-name "Asimov"
:num-of-books 500})
true
dop-examples> (associative? (->Author "Isaac" "Asimov" 500))
true
24
(書籍より) 利点とコスト
主な利点
特定のユースケースに限定されないジェネリック
関数を利?できる
柔軟なデータモデル
主なコスト
パフォーマンスが少し低下する
データスキーマがない
コンパイル時にデータの有効性が確認されない
静的に型付けされる?語では、明?的な型変換
キャスト が必要になることがある
25
Kotlinの場合
構造的型や拡張可能レコードのサポートがなく、後
述のデータスキーマを記述するのも?般的ではない
主体で具体的な型としてデータを定義
するのが妥当そう 適宜インターフェース化しうる
> data class Author(
val firstName: String,
val lastName: String,
val numOfBooks: Int?,
)
> Author("Isaac", "Asimov", 500)
res6: Line_0.Author = Author(firstName=Isaac, lastName=Asimov
, numOfBooks=500)
26
原則 #3: データはイミュータブルである
27
Clojureの場合
マップほか は不変
安全であり 実?上 ?分に効率的でもある
ネイティブのデータ構造
;; 関数 assoc は連想データのエントリーをupsertする
dop-examples> (assoc {:first-name "Isaac"
:last-name "Asimov"
:num-of-books 500}
:num-of-books 100)
{:first-name "Isaac", :last-name "Asimov", :num-of-books 100}
dop-examples> (let [data {:first-name "Isaac"
:last-name "Asimov"
:num-of-books 500}
data' (assoc data
:num-of-books 100)]
(identical? data data'))
false ; 参照が異なる別のデータ(永続データなので内部的には共有がある)
28
(書籍より) 利点とコスト
主な利点
すべての関数から?信を持ってデータにアクセス
できる
コードの振る舞いが予測可能である
等価のチェックが?速である
並?処理の安全性が?動的に確保される
主なコスト
パフォーマンスが低下する
永続的なデータ構造のためのライブラリが必要で
ある
29
Kotlinの場合
再代?不可な プロパティとほぼ不変単に の場合あり な
データ構造を利?することはできる
> val data1 = Author("Isaac", "Asimov", 500)
> val data2 = data.copy(numOfBooks = 100)
> data1 === data2
res9: kotlin.Boolean = false // 参照が異なる別のデータ
ミュータビリティとイミュータビリティの狭間 関
数型?語使いから?た コレクション
30
原則 #4: データスキーマをデータ表現から切り離す
31
Clojureの場合
ライブラリ が標準で
含まれており、広く使われている
契約プログラミング
(ns dop-examples
(:require
[clojure.spec.alpha :as s] ; clojure.specの導?
[clojure.string :as str]))
(defn make-author [first-name last-name num-of-books]
{:first-name first-name
:last-name last-name
:num-of-books num-of-books})
(defn full-name [{:keys [first-name last-name]}]
(str first-name " " last-name))
(defn prolific? [{:keys [num-of-books]}]
(or (some-> num-of-books (> 100))
false))
32
データの仕様
述語 により値レベルの制約まで記述できる
(s/def ::name
(s/and string?
(complement str/blank?) ; 空?字列/空?のみでない
#(<= (count %) 100))) ; ?さが100以下
(s/def ::first-name ::name)
(s/def ::last-name ::name)
(s/def ::num-of-books
(s/nilable ; nilになりうる
(s/and nat-int? ; (0を含む)?然数
#(<= % 10000)))) ; 10000以下
(s/def ::author
(s/keys :req-un [::first-name ; 列挙したキーを必ず含む
::last-name]
:opt-un [::num-of-books])) ; 列挙したキーを任意で含む
33
関数の仕様
データの仕様で関数の?出?仕様を記述できる
(s/fdef make-author
:args (s/cat :first-name ::first-name ; 第1引数
:last-name ::last-name ; 第2引数
:num-of-books ::num-of-books) ; 第3引数
:ret ::author) ; 戻り値
(s/fdef full-name
:args (s/cat :data (s/keys :req-un [::first-name
::last-name]))
:ret string?)
(s/fdef prolific?
:args (s/cat :data (s/keys :req-un [::num-of-books]))
:ret boolean?)
34
データに対する検証
;; 必須の :last-name キーが?けたマップの場合
dop-examples> (s/explain ::author {:first-name "Isaac"
:num-of-books 500})
{:first-name "Isaac", :num-of-books 500} - failed:
(contains? % :last-name) spec: :dop-examples/author
nil
35
;; :num-of-books の値が負の数の場合
dop-examples> (s/explain ::author {:first-name "Isaac"
:last-name "Asimov"
:num-of-books -1})
-1 - failed: nat-int? in: [:num-of-books] at: [:num-of-books
:clojure.spec.alpha/pred] spec: :dop-examples/num-of-books
-1 - failed: nil? in: [:num-of-books] at: [:num-of-books
:clojure.spec.alpha/nil] spec: :dop-examples/num-of-books
nil
36
関数 引数 に対する検証
;; 関数の引数に対するチェックを有効化
dop-examples> (clojure.spec.test.alpha/instrument)
[dop-examples/make-author dop-examples/full-name
dop-examples/prolific?]
;; 第1引数(first-name)が空の場合
dop-examples> (make-author "" "Asimov" 500)
Execution error - invalid arguments to
dop-examples/make-author at (REPL:103).
"" - failed: (complement blank?) at: [:first-name] spec:
:dop-examples/name
37
;; キーにtypoがある(必須の :first-name キーがない)場合
dop-examples> (full-name {:fist-name "Isaac"
:last-name "Asimov"})
Execution error - invalid arguments to dop-examples/full-name
at (REPL:106).
{:fist-name "Isaac", :last-name "Asimov"} - failed:
(contains? % :first-name) at: [:data]
38
(書籍より) 利点とコスト
主な利点
検証すべきデータを?由に選択できる
オプションフィールドを利?できる
?度なデータ検証条件を利?できる
データモデルを?動的に可視化できる
主なコスト
データとスキーマの結び付きが弱い
パフォーマンスが少し低下する
39
Kotlinの場合
などのサポートはないので、
型で表現しがたい仕様はアサーションで検証
プリミティブなデータをリッチにすることもできる
依存型
data class Author3(
val firstName: String,
val lastName: String,
val numOfBooks: Int?,
) {
init {
require(firstName.isNotBlank()
&& firstName.length <= 100)
require(lastName.isNotBlank()
&& lastName.length <= 100)
require(numOfBooks?.let { it in 0..10000 } ?: true)
}
}
40
3. まとめ
41
「データ指向プログラミング」は ?語 コミ
ュニティに由来するプログラミングスタイル
その?が最も効果的に発揮されるのは
を使うときかも もいいぞ
のような静的型付きオブジェクト指向?語で
も こそ 参考になる?唆を含んでいる
そもそも近年の新興?語ではクラスベースの
を押し出していない印象がある。クラスと
いう枠組みでのモデル化に囚われる必要はない
42
Further Reading
データ指向プログラミング
本発表のサンプルコード
『データ指向プログラミング』
新刊『データ指向プログラミング』から「はじめ
に」を公開! オブジェクト指向との対?も語ら
れる (コードジン)
データ指向プログラミングの真実をお話しします
43
Clojure
による解説
公式サイト
と はどう違う?
を解説 ログミー
の設計に?る という考え?
を解説 ログミー
の世界観 紙箱
44

More Related Content

Kotlin Meets Data-Oriented Programming: Kotlinで実践する「データ指向プログラミング」