Clojure Language Update 2022

今年もアドベントカレンダーの季節がやってきました。この1年のClojure/ClojureScript界隈の動きを振り返ってみましょう。

## Clojure 15周年  

今年の10月でClojureが2007年に初めてリリースされてから15年が経ちました。  
これを受けて、Clojure 15周年を祝うオンラインのイベントが開催され、開発コアチームのメンバー4人によるバネルトークが執り行われました。  

イベントの様子はYouTubeで観ることができます：  

<iframe width="560" height="315" src="https://www.youtube.com/embed/exSRG-iL74Q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>  

NubankからもClojureの歴史を振り返る記事が出されています：  
- [Clojure 15th Anniversary: A Retrospective](https://building.nubank.com.br/clojure-15th-anniversary-a-retrospective/)

## Clojure 1.11.0 & 1.11.1リリース  

今年はこれまでにClojure 1.11.0, 1.11.1、そして1.12.0-alpha1のリリースがありました。  

Clojure 1.11.0の主要な変更については昨年の [Language update](https://qiita.com/athos/items/bd0c02c7988f99ef8d25#clojure-111) の記事で紹介しましたが、記事を書いた時点から以下の変更が新たに加わっています：  

- `clojure.java.math`名前空間の名称が`clojure.math`へ変更に  
- `abs`関数を`clojure.math`から`clojure.core`へ移動  
- `iteration`関数の追加  

Clojure 1.11.1は、Clojure 1.11.0で意図せず入ってしまったJavaレベルのシリアライズ機能に対する非互換な変更を修正するためのリリースでした。

参考：

- [Changes to Clojure in Version 1.11.1](https://github.com/clojure/clojure/blob/master/changes.md#changes-to-clojure-in-version-1111)
- [CLJ-2701](https://clojure.atlassian.net/browse/CLJ-2701)

### `iteration`  

Clojure 1.11.0で追加された`iteration`について補足しておくと、`iteration`は「内部状態を持ったバージョンの`iterate`関数」といったような立ち位置のシーケンス生成関数[^1]です。特に、ページングされたAPI呼び出しの結果をシーケンスにまとめたいような場合に便利に使える関数です。

[^1]: 厳密には`seqable`を生成する関数

たとえば、以下のようにページングされたweb APIの呼び出しで、次のページの結果をリクエストする際には結果と一緒に返ってくるトークンをリクエストに付与してやる必要がある場合、

```clojure
(require '[cognitect.aws.client.api :as aws])
				  				  
(def client (aws/client {:api :s3}))
				  				  
(aws/invoke client
            {:op :ListObjectsV2
             :request {:Bucket "my-bucket"}})
;=> {:Contents [ ... ], :NextContinuationToken "<next token>", ...}

(aws/invoke client
            {:op :ListObjectsV2
             :request {:Bucket "my-bucket" :ContinuationToken "<next token>"}})
;=> {:Contents [ ... ], :NextContinuationToken "<another next token>", ...}
```

このweb APIから返る結果全体からなるシーケンスを作ろうとすると、自分で再帰関数を書いて遅延シーケンスを作るか、

```clojure
(defn fetch-bucket-objects [client bucket]
  (letfn [(step [token]
            (let [req (cond-> {:Bucket bucket}
                        token (assoc :ContinuationToken token})
                  res (aws/invoke client {:op :ListObjectsV2 :request req})]
              (cons (:Contents res)
                    (when-let [token (:NextContinuationToken res)]
                      (lazy-seq (step token))))))]
    (sequence cat (step nil))))
```

あるいは、`iterate`をやや技巧的に使う必要がありました。

```clojure
(defn fetch-bucket-objects [client bucket]
  (let [req {:Bucket bucket}]
    (->> (aws/invoke client {:op :ListObjectsV2 :request req})
         (iterate
          (fn [res]
            (when-let [token (:NextContinuationToken res)]
              (aws/invoke client
                          {:op :ListObjectsV2
                           :request (assoc req :ContinuationToken token)}))))
         (sequence (comp (take-while seq) (mapcat :Contents))))))
```

`iteration`を使うと、このような場合に自分で再帰を書く必要もなく、ストレートに処理を書き下すことができます：

```clojure
(defn fetch-bucket-objects [client bucket]
  (->> (iteration (fn [token]
                    (let [req (cond-> {:Bucket bucket}
                                token (assoc :ContinuationToken token))]
		              (aws/invoke client {:op :ListObjectsV2 :request req})))
                  :vf :Contents
                  :kf :NextContinuationToken)
       (eduction cat)))
```

オプショナル引数として指定する関数群が若干分かりづらいので少しとっつきづらい印象ですが、うまくハマる部分で使えると強力な道具になりそうです。  

`iteration`の使い方についてより詳しく知りたい方には、以下の紹介記事が参考になるでしょう：

- [The new Clojure 'iteration' function](https://www.juxt.pro/blog/new-clojure-iteration/)
- [ELI5: Clojure's Iteration function](https://www.abhinavomprakash.com/posts/clojure-iteration/)

## Clojure 1.12  

次期バージョンであるClojure 1.12は今までに1.12.0-alpha1が出ている状況で、ユーザレベルで分かりやすい変更としては次のような機能追加・改善がなされています：  

- `partitionv`, `partitionv-all`, `splitv-at`の追加  
- `empty?`のtransient対応  

以下でそれぞれについて詳しく見ていきましょう。

### `partitionv`, `partitionv-all`, `splitv-at`  

Clojure 1.12では、新たに`partitionv`, `partitionv-all`, `splitv-at`という3つの関数が追加されました。これらは、既存の関数である`patition`, `partition-all`, `split-at`と名前も挙動もよく似ています：  

``` clojure
(partitionv 3 (range 10))
;=> ([0 1 2] [3 4 5] [6 7 8])

(partitionv-all 3 (range 10))
;=> ([0 1 2] [3 4 5] [6 7 8] [9])

(splitv-at 5 (range 10))
;=> ([0 1 2 3 4] (5 6 7 8 9))
```

既存の関数との違いとしては、(名前に含まれる`v`が示しているように)シーケンスの要素がベクタである点がまず目につきますが、実はパフォーマンスの改善が導入の大きなモチベーションになっています。  `partition`等の既存の関数は、内部的には`take`と`drop`の組み合わせによって作られていました。しかし、これには以下のようなパフォーマンス上の欠点がありました：  

- 先頭のn個の要素をまとめる際に遅延シーケンスを使うのは効率が悪い  
- 先頭のn個の要素をパーティションにまとめる(`take`)のと、先頭のn個の要素をスキップする(`drop`)ために、同じシーケンスを2回たどる必要があり非効率  

`partitionv`等の新たに追加された関数では、これらに対して以下のような改善になされ、パフォーマンスの向上が図られています：  

- 先頭のn個の要素をまとめるのに、`(take n coll)`ではなく`(into [] (take n) coll)`のようにトランスデューサを使ってベクタにまとめることで効率を改善  
- 新たに`IDrop`という、定数時間で`drop`ができるインタフェースが追加され、このインタフェースを実装した`range`やchunked sequence等のシーケンスに対する`drop`でシーケンスを逐次的にたどる必要性をなくした  

これらの改善により、新たに追加された関数たちは既存の関数と比較して、条件によっては2倍以上速くなることがあるとされています[^benchmark-result]：  

| coll | `partition-all` (ms) | `partitionv-all` (ms) |
| :--- | -------------------: | --------------------: |
| `range` | 561 | 217 |
| `repeat` | 692 | 208 |
| `vector` | 756 | 239 |
| `string` | 782 | 247 |
| `array-map` | 795 | 260 |
| `seq` | 790 | 385 |

[^benchmark-result]: テーブルのベンチマーク結果は https://insideclojure.org/2022/06/15/partitioning/ から抜粋

既存の関数は後方互換性のために維持されていますが、新しい関数には特に目立った欠点もないので、今後は`partition`等を使ったコードが今回追加された関数たちに置き換えられていくことが予想されます。  

参考：

- [Efficient partitioning](https://insideclojure.org/2022/06/15/partitioning/)

### `empty?`のtransient対応  

これまで、[transient](https://clojure.org/reference/transients)コレクションに対して`empty?`を呼ぶとエラーになる問題がありました：  

```clojure
(def xs (transient []))
				  				  
(empty? xs)
;; Execution error (IllegalArgumentException) at user/eval139 (REPL:1).
;; Don't know how to create ISeq from: clojure.lang.PersistentVector$TransientVector
```

これは、`empty?`が `#(not (seq %))` として定義されていて、transientが`seq`に対応していない(seqableでない)ことが原因でした。

1.12では、`empty?`への入力が`Counted`、つまり定数時間で要素数が取得できるデータ構造である場合には `#(zero? (count %))` として空かどうかを判定するよう変更され、これによって、(`Counted`である)transientに対して`empty?`が呼べるようになりました。

また、この変更によって、transientだけでなく`Counted`なデータ型に対して空判定をする場合には、`seq`を使うよりも`empty?`を使った方が効率がよくなるようになりました。従来のClojureのイディオムでは、コレクションの空判定には`empty?`ではなく`seq`を使うべきだとされてきましたが、今後はベクタやマップ等、多くのデータ型に対しては`empty?`を使うことが推奨されるようになりそうです[^2]。

[^2]: なお、シーケンスは`Counted`ではないので、この変更が加えられた後でも従来通り`seq`を使うようにした方がよいでしょう。

参考：

- [Clojure 1.12.0-alpha1](https://clojure.org/news/2022/06/30/clojure1-12-alpha1)

## ClojureDart  

今年4月、以前から開発が進められているといわれていたClojureDartがOSSとして公開されました。  

[ClojureDart](https://github.com/Tensegritics/ClojureDart) はChristophe Grand ([@cgrand](https://twitter.com/cgrand)) 氏らが非公式に開発しているClojureからDartへのトランスレータです。非公式の実装ではありますが、Flutterを通じてClojureでモバイルアプリ開発をする手段の1つとして注目を集めていて、Clojurists TogetherやNubankからもスポンサーを受けています。  

現状のステータスとしては「[まだ開発途上](https://github.com/Tensegritics/ClojureDart#maturity)」だとしていますが、リスクを厭わないClojuristであれば十分実運用可能な状態だとも言っています。実際、ナレッジ管理ツールのRoam Researchは2021年からすでに[ClojureDartでモバイルアプリを開発し、リリースしている](https://github.com/Tensegritics/ClojureDart#thanks)とのことです。

## Borkdudeプロダクト、今年も続々登場

[Babashka](https://github.com/babashka/babashka) や [clj-kondo](https://github.com/clj-kondo/clj-kondo), [jet](https://github.com/borkdude/jet) などのOSSの開発者として知られるBorkdude ([@borkdude](https://github.com/borkdude))ことMichiel Borkent氏は今年も開発の勢いが止まりませんでした。  
Borkdude氏はもともとClojureコミュニティ全体で見ても生産性の高いプログラマでしたが、GitHub SponsorsやClojurists Together, Nubankをはじめとした多くのスポンサーの支援により、昨年下期からはフルタイムでOSS開発に取り組み始めたことでますます勢いに拍車がかかっているものと思われます。

今年、Borkdude氏が作ったプロダクトには次のようなものがありました[^3]：

[^3]: すべてのプロダクトを網羅したリストではありません。

- [lein2deps](https://github.com/borkdude/lein2deps): `project.clj`から`deps.edn`へのコンバータ  
- [babashka.cli](https://github.com/babashka/cli): ClojureとBabashkaから使えるコマンドライン引数パーサ  
- [quickdoc](https://github.com/borkdude/quickdoc): 軽量なドキュメント生成ツール  
- [quickblog](https://github.com/borkdude/quickblog): Babashkaで動かせる軽量な静的ブログエンジン  
- [neil](https://github.com/babashka/neil): `deps.edn`ベースのプロジェクトの作成や管理等の共通処理のためのCLIツール  
- [squint](https://github.com/squint-cljs/squint): ClojureScript風のシンタックスからJavaScriptへのコンパイラ  
- [cherry](https://github.com/squint-cljs/cherry): ClojureScriptからES6モジュールへのコンパイラ  
- [joyride](https://github.com/BetterThanTomorrow/joyride): VS CodeをClojureScriptで操作するためのVS Code拡張  

また、Babashkaの発展としては、新たに [bbin](https://github.com/babashka/bbin) というBabashkaスクリプトをCLIコマンドとしてインストールできるインストーラが登場し、Babashkaとしてのエコシステムも広がりを見せています。こちらの動向にも要注目です。  

## SciCloj 台頭

今年はSciClojが活発に活動していた年でもありました。
[SciCloj](https://scicloj.github.io/) はClojureコミュニティの中で特にデータサイエンスや科学計算領域に関する知識や技術の共有・展開を目的としているグループです。  

今年のSciClojの主な活動としては以下のようなものがありました：

- [`tablecloth`](https://github.com/scicloj/tablecloth)や[`scicloj.ml`](https://github.com/scicloj/scicloj.ml)などのOSS開発  
- データサイエンス等の特定領域をテーマにした勉強会の開催  
  - [visual-tools](https://scicloj.github.io/docs/community/groups/visual-tools/): データ可視化ツールやノートブックツール等について学ぶ勉強会  
  - [data-recur](https://scicloj.github.io/docs/community/groups/data-recur/): データ処理向けのClojure技術スタックについて議論する勉強会  
  - [jointprob](https://scicloj.github.io/docs/community/groups/jointprob/): 確率モデルやベイズ統計等の全般的な勉強会  
- Clojurists Togetherの支援の下で [Clojure Data Cookbook](https://github.com/scicloj/clojure-data-cookbook) の編纂  

SciClojのメンバーはClojure Zulipの #data-science ストリームでも日常的に活発にやりとりがされているので興味があれば覗いてみるといいでしょう。

データサイエンス領域については、この記事冒頭で紹介したClojure 15周年のイベントでRich Hickeyも「Clojureのデータサイエンス領域での活用に期待している」とコメントしていたり、[Clerk](https://github.com/nextjournal/clerk) や [Portal](https://github.com/djblue/portal)、[Data Rabbit](https://datarabbit.com/) のようなデータ可視化ツール・ノートブックツールが続々と登場したりしている状況で、今後のさらなる発展が楽しみな領域の1つです。  

## Clouncil 開始

今年3月からClouncilというYouTube番組が開始されました。  
[Clouncil](https://theclouncil.com/) は、Clojureを学び始めたばかりの人がClojureのプロにリアルタイムに質問や相談をして、問題の解決を手助けしてもらえるコールインショー(視聴者参加型番組)です。

相談は以下のような出演メンバーによって受けつけられています：  

- Arne Brasseur: Lambda Islandの中の人  
- Mike Fikes: ClojureScriptのメイン開発者の1人  
- Paula Georon: asami等のOSS開発者  
- Daniel Higginbotham: Clojure for the Brave and Trueの著者  
- Jordan Miller: ClojureのPodcast "Lost in Lambduhhs"のホスト  

開催は不定期で、数週間〜数ヶ月に1回程度の頻度で配信されています。

## Clojurians SlackがProプランに

Clojureコミュニティでは、[Clojurians Slack](https://clojurians.slack.com)というSlackワークスペースが2015年から存在し、コミュニティの主要なコミュニケーションの場として使われています。  開発チームのメンバー(特にAlex Miller)や有名OSSの開発者がよく現れることから、初心者の質問からプロダクト開発でのかなりつっこんだ問題の相談まで、幅広く様々なやりとりが活発にされています。  

しかし、Clojurians Slackはコミュニティ主導で無料プランで運営されていたため、過去にやりとりされたメッセージのログにアクセスできなくなるなどの問題がありました[^4]。

[^4]: メッセージのログにアクセスできなくなる問題に対しては、ボットを常駐させて[Zulip](https://clojurians.zulipchat.com/)に転送してアーカイブを取ったり、[独自にログを集積](https://clojurians-log.clojureverse.org/)したりする取り組みは行われていました  

そんななか、昨年12月にSlack公式からの計らいでスポンサーを受けられることが決まり、Clojurians SlackはProプランへ移行することができました。ClojureコミュニティにおけるClojurians Slackの存在感の大きさを思うと、この計らいによる継続性の向上はとてもありがたいことです。ありがとう、Slack！  

<blockquote class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com/stewart?ref_src=twsrc%5Etfw">@stewart</a> Thank you so much, this really makes a big difference to the Clojurians Slack community! <a href="https://twitter.com/hashtag/clojure?src=hash&amp;ref_src=twsrc%5Etfw">#clojure</a> <a href="https://t.co/6hdhoyTcc9">pic.twitter.com/6hdhoyTcc9</a></p>&mdash; @borkdude@mastodon.social (@borkdude) <a href="https://twitter.com/borkdude/status/1466486985032355846?ref_src=twsrc%5Etfw">December 2, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## おわりに

来年4月はClojure/conjが3年ぶりの開催が予定されています。来年もClojure界隈でのさまざまな盛り上がりを期待していきたいですね！