Upgrade to Pro — share decks privately, control downloads, hide ads and more …

do Notation Equivalents in JVM languages: Scala...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Kent OHASHI Kent OHASHI
November 22, 2024

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

Haskellでお馴染みのdo記法(do notation)がJVM言語Scala, Kotlin, Clojureでは言語機能やライブラリ実装としてどのように実現されているか、簡単に探ってみよう。

Avatar for Kent OHASHI

Kent OHASHI

November 22, 2024

More Decks by Kent OHASHI

Other Decks in Programming

Transcript

  1. 簡潔に書き換える構⽂として 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]
  2. モナドに相当する構造を扱い始めると 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
  3. 簡潔に書き換える構⽂として 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
  4. 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
  5. Iterableに対して >>> listOf(1, 2, 3).flatMap { x -> ... listOf(4,

    5).map { y -> ... x * y ... } ... } res2: kotlin.collections.List<kotlin.Int> = [4, 5, 8, 10, 12, 15] 12
  6. ライブラリ の 関数を利⽤する 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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