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

From Scala/Clojure to Kotlin

From Scala/Clojure to Kotlin

Scala/Clojureでの開発経験を経てKotlinに入門して感じた印象を簡単にまとめました。

Kent OHASHI

February 28, 2025
Tweet

More Decks by Kent OHASHI

Other Decks in Programming

Transcript

  1. のシニアエンジニア 主要技術スタック: Kotlin + の運営企業 関数型言語/ 関数型プログラミングが好き 仕事や趣味で , ,

    などに長く 触れてきた(JVM 言語の割合高め) 現職で初めて仕事でKotlin を扱うようになってそろ そろ1 年 lagénorhynque 🐬カマイルカ 株式会社スマートラウンド Ktor Server-Side Kotlin Meetup Clojure Scala Haskell 2
  2. paradigm OOP, FP FP OOP typing static dynamic static first

    appeared 2004 2007 2011 designer Martin Odersky Rich Hickey JetBrains 🐬's note OOPL in FPL's skin modern functional Lisp better Java influenced by Scala Scala Clojure Kotlin 5
  3. クラス・メソッド/ 関数・変数の定義 /* Scala */ scala> case class Person( |

    val name: String, | val birthDate: LocalDate, | ): | def age(now: LocalDate): Int = ChronoUnit.YEARS .between(birthDate, now).toInt // defined case class Person scala> val 🐬 = Person("lagénorhynque", LocalDate.of(1990, 1, 18)) val 🐬: Person = Person(lagénorhynque,1990-01-18) scala> 🐬.name val res0: String = lagénorhynque scala> 🐬.age(LocalDate.now) val res1: Int = 35 7
  4. 基本的な構文は酷似している Scala 3 では も導入された Scala では0 引数メソッド呼び出しで括弧が省略可能 /* Kotlin

    */ >>> data class Person( ... val name: String, ... val birthDate: LocalDate, ... ) { ... fun age(now: LocalDate): Int = ChronoUnit.YEARS .between(birthDate, now).toInt() ... } >>> val ` 🐬` = Person("lagénorhynque", LocalDate.of(1990, 1, 18)) >>> ` 🐬`.name res4: kotlin.String = lagénorhynque >>> ` 🐬`.age(LocalDate.now()) res5: kotlin.Int = 35 オフサイドルール 8
  5. 関数リテラル( ラムダ式) と高階関数 丸括弧や矢印、destructuring ( 分配束縛) の差異に よく戸惑う /* Scala

    */ scala> (1 to 10). // REPL でのメソッドチェーンのため . が末尾にある | filter(_ % 2 != 0). | map(x => x * x). | foldLeft(0)((acc, x) => acc + x) val res0: Int = 165 /* Kotlin */ >>> (1..10). ... filter { it % 2 != 0 }. ... map { x -> x * x }. ... fold(0) { acc, x -> acc + x } res0: kotlin.Int = 165 9
  6. メソッドの関数としての利用 Scala ではメソッド名そのままで関数オブジェクト として参照できる(ref. ) /* Scala */ scala> def

    factorial(n: Long): Long = (1L to n).product def factorial(n: Long): Long scala> (0L to 9L).map(factorial) val res0: IndexedSeq[Long] = Vector(1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880) /* Kotlin */ >>> fun factorial(n: Long): Long = (1L..n).fold(1L) { acc, x -> acc * x} >>> (0L..9L).map(::factorial) res1: kotlin.collections.List<kotlin.Long> = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] eta-expansion 10
  7. 文指向ではなく式指向 コードブロックで明示的な return を要求される ことによく戸惑う Scala では常に最後の式が戻り値になり、 return はめったに使わない /*

    Scala */ // import scala.math.sqrt scala> def isPrime(n: Int): Boolean = | if (n < 2) false else !(2 to sqrt(n).toInt) .exists(n % _ == 0) // if は式 def isPrime(n: Int): Boolean /* Kotlin */ // import kotlin.math.sqrt >>> fun isPrime(n: Int): Boolean = ... if (n < 2) false else !(2..sqrt(n.toDouble()) .toInt()).any { n % it == 0 } // if は式 11
  8. Option type vs nullable type Scala の 型は Some(x), None

    の値をとり、 コレクションなどと同じように扱う 参照型で null が代入できてしまうという点で null-safe ではない( が、習慣として常に避ける) /* Scala */ // import.scala.math.pow scala> (Some(2): Option[Int]).map(pow(_, 10)) val res0: Option[Double] = Some(1024.0) scala> (None: Option[Int]).map(pow(_, 10)) val res1: Option[Double] = None /* Kotlin */ // import kotlin.math.pow >>> (2 as Int?)?.let { it.toDouble().pow(10.0) } res1: kotlin.Double = 1024.0 >>> (null as Int?)?.let { it.toDouble().pow(10.0) } res2: kotlin.Double = null Option 12
  9. for 式(a.k.a. for 内包表記) モナド( 的な構造) を簡潔に扱うためのシンタックス シュガーとして非常に便利 に相当するもの scala>

    for // Range (Seq) 型の場合 | x <- 1 to 3 | y <- 4 to 5 | yield x * y // => 1*4, 1*5, 2*4, 2*5, 3*4, 3*5 val res0: IndexedSeq[Int] = Vector(4, 5, 8, 10, 12, 15) scala> for // Option 型(Kotlin ではnullable type で表すもの) の場合 | x <- Some(2) | y <- Some(3) | yield x * y // => Some(2*3) val res1: Option[Int] = Some(6) scala> for | x <- Some(2) | y <- None: Option[Int] | yield x * y // => 全体としてNone に val res2: Option[Int] = None Haskell のdo 記法 14
  10. パターンマッチング( 主にmatch 式) 構造に基づいて場合分けしながら分解できる 代数的データ型を扱う上で常にセットでほしい存在 コンパイラによる網羅性チェックも行われる scala> enum Tree[+A]: //

    書籍FP in Scala のサンプルコードより引用 | case Leaf(value: A) | case Branch(left: Tree[A], right: Tree[A]) | | def depth: Int = this match | case Leaf(_) => 0 | case Branch(l, r) => 1 + (l.depth.max(r.depth)) // defined class Tree scala> import Tree._ scala> Branch(Leaf("a"), Branch(Branch(Leaf("b"), Leaf("c")) , Leaf("d"))).depth val res0: Int = 3 15
  11. Lisp マクロ シンタックスレベルでよくあるパターンを抽象化し たい場合が稀にある とはいえ(Lisper の良識として) マクロ定義は抑制 的であるべき user> (defmacro

    unless [test & body] ; 標準ライブラリのwhen-not `(when (not ~test) ~@body)) #'user/unless user> (unless (= 1 2) (println "Falsy!")) Falsy! nil user> (unless (= 1 1) (println "Falsy!")) nil user> (macroexpand-1 '(unless (= 1 2) (println "Falsy!"))) (clojure.core/when (clojure.core/not (= 1 2)) (println "Falsy!")) 18
  12. Further Reading Scala 公式サイト: cf. https://www.scala-lang.org/ For Comprehensions | Tour

    of Scala | Scala Documentation do Notation Equivalents in JVM languages: Scala, Kotlin, Clojure | ドクセル Pattern Matching | Tour of Scala | Scala Documentation 20
  13. Clojure 公式サイト: cf. ref. https://clojure.org/ Clojure - Programming at the

    REPL: Introduction Clojure でRDD とTDD のハイブリッドな開発ス タイルを実践しよう Clojure - Macros defmacro - clojure.core | ClojureDocs - Community-Powered Clojure Documentation and Examples 21