JavaからScala、そしてClojureへ
実務で活きる関数型プログラミング
lagénorhynque 🐬カマイルカ
(defprofile lagénorhynque

:id @lagenorhynque

:readings ["/laʒenɔʁɛ̃
k/" "ラジェノランク"]

:aliases ["カマイルカ" "🐬"]

:languages [Java Japanese ; native languages

Clojure Haskell ; functional languages

English français] ; European languages

:interests [programming

language-learning

law politics

mathematics])
1. 私と仙台
2. 私と関数型言語
3. Opt Technologiesと関数型言語
4. 関数型プログラミングの実践例
5. 関数型プログラミング的な発想
私と仙台
プライベート
岐阜出身
2012年春〜: 東京
2022年春〜: 千葉
仙台/宮城/東北に接点は(たぶん)なさそう
が以前から気になっている🐬
仙台うみの杜水族館
仕事(オプト)
オプトの は主に仙台拠点で
開発運用されていた
2017年頃から東京の開発部門も関わり始める
そのタイミングで東京所属の私もジョイン
2018年には仙台拠点へ出張する機会も
2021年にフルリモートワーク前提で東京と仙台
の開発部門が統合された
現在も仙台在住のメンバーと日常的に一緒に仕事
している
広告運用支援ツール群
私と関数型言語
year event
2011 大学(法学部) 4年で初めてプログラミングに
少し触れる: SQL, Java
2012 前職の会社に新卒入社し、
Javaでの業務システム開発に携わり始める
2014 趣味で関数型言語に傾倒する:
Haskell, Clojure, Erlang, Scala, OCaml, etc.
2015 Clojure, Haskell, Scalaの勉強会に参加する
ようになり、のちの同僚とも出会う
year event
2016 オプトに中途入社し、
大規模なScala開発を経験する
2017 開発中のプロダクトの小さなバッチに
Clojureを社内初導入する
2018 新規プロダクトのREST API実装にClojureを
採用する
2019 新規プロダクトのGraphQL API実装に
Clojureを採用する
2021 開発チームを離れ、開発組織横断的な技術
マネジメント業務へ
発表:
at on 2017/01/28
JavaプログラマこそClojureを始めようという悪魔
の誘い😈
JavaからClojureへ
第十八回#渋谷java
発表:
at on 2019/07/25
オプトでのClojure採用から普及の歴史⚔️について
ジョーク成分多めに紹介
Clojurian Conquest
Shibuya.lisp lispmeetup #78
記事: Clojureをプロダクトに導入した話
記事: サービス間連携のためのGraphQL APIをClojure
で開発している話
Opt Technologiesと関数型言語
社内での関数型言語利用の歴史
発足(2016年)以前から前身とな
った開発会社でメイン開発言語だった
近年はバックエンド開発の利用言語が多様化して
いるが、引き続き主要言語のひとつ
2017年の導入からシェアが拡大し、重要なプロ
ダクトを支える言語のひとつになった
当初は一人しか経験者がいなかったが、継続的に
開発可能な体制に成熟してきた
Scala
Opt Technologies
Clojure
2018年頃に導入を試みたが、プロダクト開発が
諸事情により中止になり現存しない😇
開発者向け管理画面のために小さく使われている
例がある
上記HaskellプロダクトのWebフロントエンドに
も採用されていた
cf.
Haskell
Elm
Opt Technologiesの主な利用技術
関数型プログラミングの実践例
Java
オブジェクト指向・非関数型言語
静的型付き言語
JVM言語
関数型プログラミング関連機能が充実してきた
cf.
// メソッドの定義

jshell> void hello(Object x) {

...> System.out.println("Hello, %s!".formatted(x));

...> }

| created method hello(Object)

// メソッドの呼び出し

jshell> hello("Java")

Hello, Java!
jshell コマンド
Scala
オブジェクト指向・関数型言語
静的型付き言語
JVM言語
オブジェクト指向に関数型が溶け込んだ言語
cf.
// メソッドの定義

scala> def hello(x: Any): Unit =

| println(s"Hello, $x!")

|

def hello(x: Any): Unit

// メソッドの呼び出し

scala> hello("Scala")

Hello, Scala!
scala コマンド
Clojure
非オブジェクト指向・関数型言語
動的型付き言語
JVM言語
オブジェクト指向を嫌い関数型を志向したLisp
cf. +
;; 関数の定義

user=> (defn hello [x]

#_=> (println (str "Hello, " x "!")))

#'user/hello

;; 関数の呼び出し(適用)
user=> (hello "Clojure")

Hello, Clojure!

nil
clojure コマンド rebel-readline
とあるプロダクトのJavaコード(抜粋)
return mediaProcessLogDao.selectMediaProcessLogs(baseDate,

modifiedEntities).stream()

.map(this::normalizeTargetIndexIfAdvertise)

.collect(Collectors.groupingBy(MediaProcessLogEntity::getKey))
.entrySet().stream().collect(toMap(

Map.Entry::getKey,

group -> {

List<MediaProcessLogEntity> entities = group.getValue();

if (entities.stream()

.allMatch(MediaProcessLogEntity::isEmpty)) {

return true;

}

return entities.stream()

.filter(e -> !e.isEmpty())

.allMatch(MediaProcessLogEntity::isImported);

}));
問題を単純化すると
エンティティのリストをその要素のキーごとにグルー
ピングし、個々のグループの値が特定の条件を満たす
かどうかを表す対応表(マップ)がほしい。
どのようなプログラムに落とし込む?
Java: サンプルデータ
jshell> record Entity(int key, String x) {}

| created record Entity

jshell> final var entities = List.of(

...> new Entity(3, "a"),

...> new Entity(1, "b"),

...> new Entity(2, "c"),

...> new Entity(1, "d"),

...> new Entity(1, "e")

...> )

entities ==> [Entity[key=3, x=a], Entity[key=1, x=b],

Entity[k ... x=d], Entity[key=1, x=e]]
Java: 命令型(imperative)のアプローチ
jshell> final var keyToEntities =

...> new HashMap<Integer, List<Entity>>();

...> for (final var e : entities) {

...> final var es = keyToEntities.getOrDefault(e.key(),

...> new ArrayList<Entity>());

...> es.add(e);

...> keyToEntities.put(e.key(), es);

...> }

keyToEntities ==> {}

jshell> keyToEntities

keyToEntities ==> {1=[Entity[key=1, x=b], Entity[key=1, x=d],

Entity[key=1, x=e]], 2=[Entity[key=2, x=c]],

3=[Entity[key=3, x=a]]}
jshell> final var result = new HashMap<Integer, Boolean>();

...> for (final var entry : keyToEntities.entrySet()) {

...> result.put(entry.getKey(),

...> entry.getValue().size() > 1);

...> }

result ==> {}

jshell> result

result ==> {1=true, 2=false, 3=false}
Java: 関数型(functional)のアプローチ
※ REPLでの行継続のため行末に. を置いている
jshell> entities.stream().

...> collect(Collectors.groupingBy(Entity::key)).

...> entrySet().stream().

...> collect(Collectors.toMap(

...> Map.Entry::getKey,

...> group -> group.getValue().size() > 1

...> ))

$3 ==> {1=true, 2=false, 3=false}
Scala: サンプルデータ
scala> case class Entity(key: Int, x: String)

// defined case class Entity

scala> val entities = Seq(

| Entity(3, "a"),

| Entity(1, "b"),

| Entity(2, "c"),

| Entity(1, "d"),

| Entity(1, "e"),

| )

val entities: Seq[Entity] = List(Entity(3,a), Entity(1,b),

Entity(2,c), Entity(1,d), Entity(1,e))
Scala: 関数型(functional)のアプローチ
scala> entities.

| groupBy(_.key).

| view.

| mapValues(_.length > 1).

| toMap

val res0: Map[Int, Boolean] = Map(1 -> true, 2 -> false,

3 -> false)
Clojure: サンプルデータ
user=> (def entities [#:entity{:key 3

#_=> :x "a"}

#_=> #:entity{:key 1

#_=> :x "b"}

#_=> #:entity{:key 2

#_=> :x "c"}

#_=> #:entity{:key 1

#_=> :x "d"}

#_=> #:entity{:key 1

#_=> :x "e"}])

#'user/entities
Clojure: 関数型(functional)のアプローチ
user=> (update-vals (group-by :entity/key entities)

#_=> #(> (count %) 1))

{3 false, 1 true, 2 false}
考察: 命令型(imperative)のアプローチ
2種類の変数とfor文によるループ処理
変数やメソッドの命名、レイアウトなどの工夫を
しないとコードの意図が埋もれがち
文(statement)が登場し、命令(コマンド)の並びとし
て表現されている
マップやリストが破壊的に更新されている: 可変
(mutable)データ
変数への再代入を封じる(Javaではfinal を付ける)
だけでも安心感が高まる
考察: 関数型(functional)のアプローチ
リストをグルーピングし、マップの値を変換すると
いう意図が関数/メソッドで表されている
引数で振る舞いを指定している: 高階関数
(higher-order function)
与えているのは無名関数(anonymous function)/
ラムダ式(lambda expression)
cf. オブジェクト指向のStrategyパターン
関数型言語では汎用的で高機能な関数/メソッド
が標準で充実している
全体が式(expression)で構成され、データの変換と
して表現されている
今回の例では途中過程にローカル変数もない
関数型言語では簡潔に関数を組み合わせ加工する
手段が豊富に用意されている
コード上で更新される変数やデータは見当たらない
調べてみると初期化以降にデータが更新されてい
ないことが分かる: 不変(immutable)データ
関数型言語ではデフォルトで変数は再代入でき
ず、不変データを利用しやすくなっていることが
多い
関数型プログラミング的な発想
「イミュータビリティ」と「コンポーザビリティ」を
重視する
イミュータビリティ(immutability; 不変性)
形容詞形: イミュータブル(immutable; 不変)
対義語: ミュータビリティ(mutability; 可変性)、
ミュータブル(mutable; 可変)
もとのまま変化しない(させられない)性質
凍結するイメージ? 🧊
破壊的な更新操作(再代入、更新、削除)を提供せ
ず、作成(初期化)し、取得する(読み取る)ことに
徹する
主なメリット
可読性や変更容易性、コンポーザビリティが向上
しやすくなる
デバッグやテストも容易になる
並行プログラミング、分散システムと相性が良い
プログラミング言語に限らない例
イミュータブルインフラストラクチャ
台帳データベース、追記型のRDBテーブル設計
バージョン管理システム
コンポーザビリティ(composability; 合成可能性)
形容詞: コンポーザブル(composable; 合成可能)
要素同士が組み合わせられる性質
LEGOブロックのイメージ? 🧱
主なメリット
再利用性や拡張性が向上する
高凝集で疎結合な「モジュール」(ソフトウェア
コンポーネント)に繋がる
プログラミング言語に限らない例
Pipes & Filters
Ports & Adapters (ヘキサゴナルアーキテクチャ)
Single Responsibility Principle (SRP)
( によるプレゼン)
UNIX哲学
Simple Made Easy Rich Hickey
関数型プログラミングは楽しい😆
関数型言語は怖くない( JavaプログラマこそScalaや
Clojureを試してみよう! )。
思考のリソースを節約し、扱いやすいソフトウェアを
設計するために、その発想を活かそう。
Further Reading
コミュニティイベント
: Haskell
: Lisp系言語(Clojure, Common Lispな
ど)
: Elixir
: Scala
Haskell-jp
Shibuya.lisp
fukuoka.ex/kokura.ex/ElixirImp
rpscala
書籍
: Scala, Erlang, Clojure,
Haskell
cf. (原書続
編): Elixir, Elm, Idris
Scala
『7つの言語7つの世界』
Seven More Languages in Seven Weeks
『実践Scala入門』
『Scalaスケーラブルプログラミング第4版』
『Scala関数型デザイン&プログラミング』
Clojure
cf. (原書第3
版)
Haskell
『プログラミングClojure 第2版』
Programming Clojure, Third Edition
Getting Clojure
Clojure Applied
『[増補改訂]関数プログラミング実践入門』
『プログラミングHaskell 第2版』
『すごいHaskellたのしく学ぼう!』
『Haskell入門関数型プログラミング言語の基礎と
実践』
OCaml
Erlang
Elixir
『プログラミングの基礎』
『プログラミングin OCaml』
『プログラミングErlang』
『すごいErlangゆかいに学ぼう!』
『プログラミングElixir(第2版)』

JavaからScala、そしてClojureへ: 実務で活きる関数型プログラミング