(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])
-
私と仙台
-
私と関数型言語
-
Opt Technologiesと関数型言語
-
関数型プログラミングの実践例
-
関数型プログラミング的な発想
- プライベート
- 岐阜出身
- 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 | 開発チームを離れ、開発組織横断的な技術マネジメント業務へ |
発表: JavaからClojureへ
- at 第十八回 #渋谷java on 2017/01/28
- JavaプログラマこそClojureを始めようという悪魔の誘い😈
- at Shibuya.lisp lispmeetup #78 on 2019/07/25
- オプトでのClojure採用から普及の歴史⚔️についてジョーク成分多めに紹介
-
- Opt Technologies発足(2016年)以前から前身となった開発会社でメイン開発言語だった
- 近年はバックエンド開発の利用言語が多様化しているが、引き続き主要言語のひとつ
-
- 2017年の導入からシェアが拡大し、重要なプロダクトを支える言語のひとつになった
- 当初は一人しか経験者がいなかったが、継続的に開発可能な体制に成熟してきた
-
- 2018年頃に導入を試みたが、プロダクト開発が諸事情により中止になり現存しない😇
-
- 開発者向け管理画面のために小さく使われている例がある
- 上記HaskellプロダクトのWebフロントエンドにも採用されていた
- オブジェクト指向・非関数型言語
- 静的型付き言語
- JVM言語
- 関数型プログラミング関連機能が充実してきた
// メソッドの定義
jshell> void hello(Object x) {
...> System.out.println("Hello, %s!".formatted(x));
...> }
| created method hello(Object)
// メソッドの呼び出し
jshell> hello("Java")
Hello, Java!
cf. jshell
コマンド
- オブジェクト指向・関数型言語
- 静的型付き言語
- JVM言語
- オブジェクト指向に関数型が溶け込んだ言語
// メソッドの定義
scala> def hello(x: Any): Unit =
| println(s"Hello, $x!")
|
def hello(x: Any): Unit
// メソッドの呼び出し
scala> hello("Scala")
Hello, Scala!
cf. scala
コマンド
- 非オブジェクト指向・関数型言語
- 動的型付き言語
- JVM言語
- オブジェクト指向を嫌い関数型を志向したLisp
;; 関数の定義
user=> (defn hello [x]
#_=> (println (str "Hello, " x "!")))
#'user/hello
;; 関数の呼び出し(適用)
user=> (hello "Clojure")
Hello, Clojure!
nil
cf. clojure
コマンド + rebel-readline
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);
}));
エンティティのリストをその要素のキーごとにグルーピングし、個々のグループの値が特定の条件を満たすかどうかを表す対応表(マップ)がほしい。
どのようなプログラムに落とし込む?
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]]
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}
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}
※ REPLでの行継続のため行末に .
を置いている
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> entities.
| groupBy(_.key).
| view.
| mapValues(_.length > 1).
| toMap
val res0: Map[Int, Boolean] = Map(1 -> true, 2 -> false,
3 -> false)
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
user=> (update-vals (group-by :entity/key entities)
#_=> #(> (count %) 1))
{3 false, 1 true, 2 false}
-
2種類の変数とfor文によるループ処理
- 変数やメソッドの命名、レイアウトなどの工夫をしないとコードの意図が埋もれがち
-
文(statement)が登場し、命令(コマンド)の並びとして表現されている
-
マップやリストが破壊的に更新されている: 可変(mutable)データ
-
変数への再代入を封じる(Javaでは
final
を付ける)だけでも安心感が高まる
- リストをグルーピングし、マップの値を変換するという意図が関数/メソッドで表されている
- 引数で振る舞いを指定している: 高階関数(higher-order function)
- 与えているのは無名関数(anonymous function)/ラムダ式(lambda expression)
- cf. オブジェクト指向のStrategyパターン
- 関数型言語では汎用的で高機能な関数/メソッドが標準で充実している
-
全体が式(expression)で構成され、データの変換として表現されている
- 今回の例では途中過程にローカル変数もない
- 関数型言語では簡潔に関数を組み合わせ加工する手段が豊富に用意されている
-
コード上で更新される変数やデータは見当たらない
- 調べてみると初期化以降にデータが更新されていないことが分かる: 不変(immutable)データ
- 関数型言語ではデフォルトで変数は再代入できず、不変データを利用しやすくなっていることが多い
「イミュータビリティ」と「コンポーザビリティ」を重視する
-
形容詞形: イミュータブル(immutable; 不変)
- 対義語: ミュータビリティ(mutability; 可変性)、ミュータブル(mutable; 可変)
-
もとのまま変化しない(させられない)性質
- 凍結するイメージ? 🧊
- 破壊的な更新操作(再代入、更新、削除)を提供せず、作成(初期化)し、取得する(読み取る)ことに徹する
-
主なメリット
- 可読性や変更容易性、コンポーザビリティが向上しやすくなる
- デバッグやテストも容易になる
- 並行プログラミング、分散システムと相性が良い
- 可読性や変更容易性、コンポーザビリティが向上しやすくなる
-
プログラミング言語に限らない例
- イミュータブルインフラストラクチャ
- 台帳データベース、追記型のRDBテーブル設計
- バージョン管理システム
-
形容詞: コンポーザブル(composable; 合成可能)
-
要素同士が組み合わせられる性質
- LEGOブロックのイメージ? 🧱
-
主なメリット
- 再利用性や拡張性が向上する
- 高凝集で疎結合な「モジュール」(ソフトウェアコンポーネント)に繋がる
-
プログラミング言語に限らない例
- Pipes & Filters
- Ports & Adapters (ヘキサゴナルアーキテクチャ)
- UNIX哲学
- Single Responsibility Principle (SRP)
- Simple Made Easy (Rich Hickeyによるプレゼン)
関数型プログラミングは楽しい😆
関数型言語は怖くない( JavaプログラマこそScalaやClojureを試してみよう! )。
思考のリソースを節約し、扱いやすいソフトウェアを設計するために、その発想を活かそう。
- Haskell-jp: Haskell
- Shibuya.lisp: Lisp系言語(Clojure, Common Lispなど)
- fukuoka.ex/kokura.ex/ElixirImp: Elixir
- rpscala: Scala
- 『7つの言語 7つの世界』: Scala, Erlang, Clojure, Haskell
- cf. Seven More Languages in Seven Weeks (原書続編): Elixir, Elm, Idris