Slide 1

Slide 1 text

do Notation Equivalents in JVM languages: Scala, Kotlin, Clojure 1

Slide 2

Slide 2 text

のシニアエンジニア スタートアップの起業家と投資家のための業務効 率化 連携プラットフォームを開発している 主要技術スタック の運営企業 などの関数型⾔語と関数型プログ ラミングの実践が好き と ⾔語での開発実務 に⻑く取り組んできた lagénorhynque カマイルカ 株式会社スマートラウンド 2

Slide 3

Slide 3 text

での発表テーマ JJUG CCC 2024 Fall map関数の内部実装から探るJVM⾔語のコレクション 3

Slide 4

Slide 4 text

Haskellの do 記法 4

Slide 5

Slide 5 text

モナドを扱い始めると >>= (bind)演算⼦がネストして いく(⼀種のcallback hell) 先⾏する計算の⽂脈を引き継ぐという意味では⾃然な 表現かも 読み書きにはあまり優しくないが λ> :{ λ| -- 例としてMaybe λ| Just 2 >>= \x -> λ| Just 10 >>= \y -> λ| return $ x ^ y λ| :} Just 1024 it :: Num b => Maybe b 5

Slide 6

Slide 6 text

簡潔に書き換える構⽂として do 記法がある のように ネストしたコードではなく 命令型の プログラム⾵のフラットなコードになる λ> :{ λ| do λ| x <- Just 2 λ| y <- Just 10 λ| return $ x ^ y λ| :} Just 1024 it :: Num b => Maybe b λ> :{ -- リストに対しても同様に λ| do λ| x <- [1, 2, 3] λ| y <- [4, 5] λ| return $ x * y λ| :} [4,5,8,10,12,15] it :: Num b => [b]

Slide 7

Slide 7 text

Scalaの場合 7

Slide 8

Slide 8 text

モナドに相当する構造を扱い始めると flatMap, map がネストしていく // 例としてOption scala> Some(2).flatMap(x => | Some(10).map(y => | scala.math.pow(x, y).toInt | ) | ) val res0: Option[Int] = Some(1024) 8

Slide 9

Slide 9 text

簡潔に書き換える構⽂として for 式がある scala> for | x <- Some(2) | y <- Some(10) | yield scala.math.pow(x, y).toInt val res1: Option[Int] = Some(1024) // Seqに対しても同様に scala> for | x <- Seq(1, 2, 3) | y <- Seq(4, 5) | yield x * y val res2: Seq[Int] = List(4, 5, 8, 10, 12, 15) 9

Slide 10

Slide 10 text

Kotlinの場合 10

Slide 11

Slide 11 text

nullable (nullになりうる値)に対して >>> import kotlin.math.pow >>> (2.0 as Double?)?.let { x -> ... (10.0 as Double?)?.let { y -> ... x.pow(y).toInt() ... } ... } res1: kotlin.Int = 1024 11

Slide 12

Slide 12 text

Iterableに対して >>> listOf(1, 2, 3).flatMap { x -> ... listOf(4, 5).map { y -> ... x * y ... } ... } res2: kotlin.collections.List = [4, 5, 8, 10, 12, 15] 12

Slide 13

Slide 13 text

ライブラリ の 関数を利⽤する Arrow nullable import arrow.core.raise.nullable import kotlin.math.pow nullable { val x = (2.0 as Double?).bind() val y = (10.0 as Double?).bind() x.pow(y).toInt() } 13

Slide 14

Slide 14 text

ライブラリでの実例 らしく関数型の設計パターンを実装し ているライブラリ 関数 関数 関数 関数 関数 モナドライブラリ 関数 14

Slide 15

Slide 15 text

Clojureの場合 15

Slide 16

Slide 16 text

nilable (nilになりうる値)に対して user> (when-let [x 2] (when-let [y 10] (long (clojure.math/pow x y)))) 1024 16

Slide 17

Slide 17 text

seqable (シーケンス化できる値)に対して ;; mapcat (= map + concat)とmap user> (mapcat (fn [x] (map (fn [y] (* x y)) [4 5])) [1 2 3]) (4 5 8 10 12 15) ;; forマクロ(内包表記) user> (for [x [1 2 3] y [4 5]] (* x y)) (4 5 8 10 12 15) 17

Slide 18

Slide 18 text

モナドをプロトコルとして抽象化してみる (defprotocol Monad (return [this x]) (bind [this f m])) 18

Slide 19

Slide 19 text

do記法相当の構⽂をマクロとして定義する (defmacro mlet [monad bindings & body] (if-some [[sym m & bindings] (seq bindings)] `(bind ~monad (fn [~sym] (mlet ~monad ~bindings ~@body)) ~m) `(return ~monad (do ~@body)))) 19

Slide 20

Slide 20 text

Monad プロトコルのメソッドに対する実装を与える ;; nilableに対する実装 (def nilable-monad (reify Monad (return [_ x] (identity x)) (bind [_ f m] (when (some? m) (f m))))) ;; seqableに対する実装 (def seqable-monad (reify Monad (return [_ x] (list x)) (bind [_ f m] (mapcat f m)))) 20

Slide 21

Slide 21 text

mlet マクロを使ってみる ;; nilable値の場合 do-notation> (mlet nilable-monad [x 2 y 10] (long (clojure.math/pow x y))) 1024 ;; seqable値の場合 do-notation> (mlet seqable-monad [x [1 2 3] y [4 5]] (* x y)) (4 5 8 10 12 15) 21

Slide 22

Slide 22 text

mlet マクロを使った式を展開してみる do-notation> (clojure.walk/macroexpand-all '(mlet ...省略...)) (do-notation/bind nilable-monad (fn* ([x] (do-notation/bind nilable-monad (fn* ([y] (do-notation/return nilable-monad (do (long (clojure.math/pow x y)))))) 10))) 2) 22

Slide 23

Slide 23 text

ライブラリでの実例 の 実装 マクロ モナドライブラリ マクロ モナドを含む 圏論に由来する抽象 を扱うライブラリ マクロ 23

Slide 24

Slide 24 text

の 記法、 の 式が他⾔語でも たまにほしくなる メタプログラミングによる 構築は楽しい 24

Slide 25

Slide 25 text

Further Reading Haskell Scala 25

Slide 26

Slide 26 text

Kotlin 26

Slide 27

Slide 27 text

Clojure ドクセル 27