From Scala/Clojure
to Kotlin
#serverside_kotlin_meetup
1
のシニアエンジニア
主要技術スタック: Kotlin +
の運営企業
関数型言語/関数型プログラミングが好き
仕事や趣味で , , などに長く
触れてきた(JVM言語の割合高め)
現職で初めて仕事でKotlinを扱うようになってそろ
そろ1年
lagénorhynque🐬カマイルカ
株式会社スマートラウンド
Ktor
Server-Side Kotlin Meetup
Clojure Scala Haskell
2
[PR] カンファレンス 6月開催
Kotlin関連の も大歓迎📨
すでに複数あります😎
「関数型まつり2025」
プロポーザル
3
JVM言語Scala, Clojure, Kotlin
4
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
Scalaに(およそ)似ているところ
6
クラス・メソッド/関数・変数の定義
/* 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
基本的な構文は酷似している
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
関数リテラル(ラムダ式)と高階関数
丸括弧や矢印、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
メソッドの関数としての利用
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
文指向ではなく式指向
コードブロックで明示的な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
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
Scalaが恋しくなるところ
13
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
パターンマッチング(主に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
Clojureが恋しくなるところ
16
エディタと統合されたREPL
Lisper/Clojurianの生産性の源泉
特に局所評価(eval-last-sexp)できると非常に捗る
17
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
まとめ
JVM言語の中で相対化することで理解が深まる
Kotlinでの開発体験のさらなる改善にも期待🐤
19
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
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

From Scala/Clojure to Kotlin