今年もアドベントカレンダーの季節がやってきました。この1年のClojure/ClojureScript界隈の動きを振り返ってみましょう。
Clojure 15周年
今年の10月でClojureが2007年に初めてリリースされてから15年が経ちました。
これを受けて、Clojure 15周年を祝うオンラインのイベントが開催され、開発コアチームのメンバー4人によるバネルトークが執り行われました。
イベントの様子はYouTubeで観ることができます:
NubankからもClojureの歴史を振り返る記事が出されています:
Clojure 1.11.0 & 1.11.1リリース
今年はこれまでにClojure 1.11.0, 1.11.1、そして1.12.0-alpha1のリリースがありました。
Clojure 1.11.0の主要な変更については昨年の Language update の記事で紹介しましたが、記事を書いた時点から以下の変更が新たに加わっています:
-
clojure.java.math
名前空間の名称がclojure.math
へ変更に -
abs
関数をclojure.math
からclojure.core
へ移動 -
iteration
関数の追加
Clojure 1.11.1は、Clojure 1.11.0で意図せず入ってしまったJavaレベルのシリアライズ機能に対する非互換な変更を修正するためのリリースでした。
参考:
iteration
Clojure 1.11.0で追加されたiteration
について補足しておくと、iteration
は「内部状態を持ったバージョンのiterate
関数」といったような立ち位置のシーケンス生成関数1です。特に、ページングされたAPI呼び出しの結果をシーケンスにまとめたいような場合に便利に使える関数です。
たとえば、以下のようにページングされたweb APIの呼び出しで、次のページの結果をリクエストする際には結果と一緒に返ってくるトークンをリクエストに付与してやる必要がある場合、
(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から返る結果全体からなるシーケンスを作ろうとすると、自分で再帰関数を書いて遅延シーケンスを作るか、
(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
をやや技巧的に使う必要がありました。
(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
を使うと、このような場合に自分で再帰を書く必要もなく、ストレートに処理を書き下すことができます:
(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
の使い方についてより詳しく知りたい方には、以下の紹介記事が参考になるでしょう:
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
と名前も挙動もよく似ています:
(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倍以上速くなることがあるとされています2:
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 |
既存の関数は後方互換性のために維持されていますが、新しい関数には特に目立った欠点もないので、今後はpartition
等を使ったコードが今回追加された関数たちに置き換えられていくことが予想されます。
参考:
empty?
のtransient対応
これまで、transientコレクションに対してempty?
を呼ぶとエラーになる問題がありました:
(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?
を使うことが推奨されるようになりそうです3。
参考:
ClojureDart
今年4月、以前から開発が進められているといわれていたClojureDartがOSSとして公開されました。
ClojureDart はChristophe Grand (@cgrand) 氏らが非公式に開発しているClojureからDartへのトランスレータです。非公式の実装ではありますが、Flutterを通じてClojureでモバイルアプリ開発をする手段の1つとして注目を集めていて、Clojurists TogetherやNubankからもスポンサーを受けています。
現状のステータスとしては「まだ開発途上」だとしていますが、リスクを厭わないClojuristであれば十分実運用可能な状態だとも言っています。実際、ナレッジ管理ツールのRoam Researchは2021年からすでにClojureDartでモバイルアプリを開発し、リリースしているとのことです。
Borkdudeプロダクト、今年も続々登場
Babashka や clj-kondo, jet などのOSSの開発者として知られるBorkdude (@borkdude)ことMichiel Borkent氏は今年も開発の勢いが止まりませんでした。
Borkdude氏はもともとClojureコミュニティ全体で見ても生産性の高いプログラマでしたが、GitHub SponsorsやClojurists Together, Nubankをはじめとした多くのスポンサーの支援により、昨年下期からはフルタイムでOSS開発に取り組み始めたことでますます勢いに拍車がかかっているものと思われます。
今年、Borkdude氏が作ったプロダクトには次のようなものがありました4:
-
lein2deps:
project.clj
からdeps.edn
へのコンバータ - babashka.cli: ClojureとBabashkaから使えるコマンドライン引数パーサ
- quickdoc: 軽量なドキュメント生成ツール
- quickblog: Babashkaで動かせる軽量な静的ブログエンジン
-
neil:
deps.edn
ベースのプロジェクトの作成や管理等の共通処理のためのCLIツール - squint: ClojureScript風のシンタックスからJavaScriptへのコンパイラ
- cherry: ClojureScriptからES6モジュールへのコンパイラ
- joyride: VS CodeをClojureScriptで操作するためのVS Code拡張
また、Babashkaの発展としては、新たに bbin というBabashkaスクリプトをCLIコマンドとしてインストールできるインストーラが登場し、Babashkaとしてのエコシステムも広がりを見せています。こちらの動向にも要注目です。
SciCloj 台頭
今年はSciClojが活発に活動していた年でもありました。
SciCloj はClojureコミュニティの中で特にデータサイエンスや科学計算領域に関する知識や技術の共有・展開を目的としているグループです。
今年のSciClojの主な活動としては以下のようなものがありました:
-
tablecloth
やscicloj.ml
などのOSS開発 - データサイエンス等の特定領域をテーマにした勉強会の開催
- visual-tools: データ可視化ツールやノートブックツール等について学ぶ勉強会
- data-recur: データ処理向けのClojure技術スタックについて議論する勉強会
- jointprob: 確率モデルやベイズ統計等の全般的な勉強会
- Clojurists Togetherの支援の下で Clojure Data Cookbook の編纂
SciClojのメンバーはClojure Zulipの #data-science ストリームでも日常的に活発にやりとりがされているので興味があれば覗いてみるといいでしょう。
データサイエンス領域については、この記事冒頭で紹介したClojure 15周年のイベントでRich Hickeyも「Clojureのデータサイエンス領域での活用に期待している」とコメントしていたり、Clerk や Portal、Data Rabbit のようなデータ可視化ツール・ノートブックツールが続々と登場したりしている状況で、今後のさらなる発展が楽しみな領域の1つです。
Clouncil 開始
今年3月からClouncilというYouTube番組が開始されました。
Clouncil は、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というSlackワークスペースが2015年から存在し、コミュニティの主要なコミュニケーションの場として使われています。 開発チームのメンバー(特にAlex Miller)や有名OSSの開発者がよく現れることから、初心者の質問からプロダクト開発でのかなりつっこんだ問題の相談まで、幅広く様々なやりとりが活発にされています。
しかし、Clojurians Slackはコミュニティ主導で無料プランで運営されていたため、過去にやりとりされたメッセージのログにアクセスできなくなるなどの問題がありました5。
そんななか、昨年12月にSlack公式からの計らいでスポンサーを受けられることが決まり、Clojurians SlackはProプランへ移行することができました。ClojureコミュニティにおけるClojurians Slackの存在感の大きさを思うと、この計らいによる継続性の向上はとてもありがたいことです。ありがとう、Slack!
@stewart Thank you so much, this really makes a big difference to the Clojurians Slack community! #clojure pic.twitter.com/6hdhoyTcc9
— @borkdude@mastodon.social (@borkdude) December 2, 2021
おわりに
来年4月はClojure/conjが3年ぶりの開催が予定されています。来年もClojure界隈でのさまざまな盛り上がりを期待していきたいですね!