Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active November 11, 2022 06:56
Show Gist options
  • Save lagenorhynque/14f79fe82df425afc6fd6346106ea236 to your computer and use it in GitHub Desktop.
Save lagenorhynque/14f79fe82df425afc6fd6346106ea236 to your computer and use it in GitHub Desktop.
JavaからScala、そしてClojureへ: 実務で活きる関数型プログラミング

JavaからScala、そしてClojureへ

実務で活きる関数型プログラミング


(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])

twitter icon


  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 開発チームを離れ、開発組織横断的な技術マネジメント業務へ

func-lang-presentation-slide1


func-lang-presentation-slide2

  • at Shibuya.lisp lispmeetup #78 on 2019/07/25
  • オプトでのClojure採用から普及の歴史⚔️についてジョーク成分多めに紹介

func-lang-magazine-article1


func-lang-magazine-article2


Opt Technologiesと関数型言語


社内での関数型言語利用の歴史

func-lang-history-opt-tech


  • Scala

    • Opt Technologies発足(2016年)以前から前身となった開発会社でメイン開発言語だった
    • 近年はバックエンド開発の利用言語が多様化しているが、引き続き主要言語のひとつ
  • Clojure

    • 2017年の導入からシェアが拡大し、重要なプロダクトを支える言語のひとつになった
    • 当初は一人しか経験者がいなかったが、継続的に開発可能な体制に成熟してきた

  • Haskell

    • 2018年頃に導入を試みたが、プロダクト開発が諸事情により中止になり現存しない😇
  • Elm

    • 開発者向け管理画面のために小さく使われている例がある
    • 上記HaskellプロダクトのWebフロントエンドにも採用されていた
  • cf. Opt Technologiesの主な利用技術


関数型プログラミングの実践例


Java

  • オブジェクト指向・非関数型言語
  • 静的型付き言語
  • JVM言語
  • 関数型プログラミング関連機能が充実してきた
// メソッドの定義
jshell> void hello(Object x) {
   ...>   System.out.println("Hello, %s!".formatted(x));
   ...> }
|  created method hello(Object)

// メソッドの呼び出し
jshell> hello("Java")
Hello, Java!

cf. jshell コマンド


Scala

  • オブジェクト指向・関数型言語
  • 静的型付き言語
  • JVM言語
  • オブジェクト指向に関数型が溶け込んだ言語
// メソッドの定義
scala> def hello(x: Any): Unit =
     |   println(s"Hello, $x!")
     |
def hello(x: Any): Unit

// メソッドの呼び出し
scala> hello("Scala")
Hello, Scala!

cf. scala コマンド


Clojure

  • 非オブジェクト指向・関数型言語
  • 動的型付き言語
  • JVM言語
  • オブジェクト指向を嫌い関数型を志向したLisp
;; 関数の定義
user=> (defn hello [x]
  #_=>   (println (str "Hello, " x "!")))
#'user/hello

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

cf. 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)のアプローチ

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: サンプルデータ

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ブロックのイメージ? 🧱

  • 主なメリット

    • 再利用性や拡張性が向上する
    • 高凝集で疎結合な「モジュール」(ソフトウェアコンポーネント)に繋がる
  • プログラミング言語に限らない例


関数型プログラミングは楽しい😆

関数型言語は怖くない( JavaプログラマこそScalaやClojureを試してみよう! )。

思考のリソースを節約し、扱いやすいソフトウェアを設計するために、その発想を活かそう。

lisp-alien


Further Reading


コミュニティイベント


書籍

Scala

Clojure
Haskell

OCaml
Erlang
Elixir
#!/usr/bin/env bash
# npm install -g reveal-md
reveal-md from-java-through-scala-to-clojure.md --theme night --highlight-theme monokai-sublime -w $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment