LoginSignup
19
5

More than 1 year has passed since last update.

Clojure Language Update 2022

Last updated at Posted at 2022-12-01

今年もアドベントカレンダーの季節がやってきました。この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等の既存の関数は、内部的にはtakedropの組み合わせによって作られていました。しかし、これには以下のようなパフォーマンス上の欠点がありました:

  • 先頭の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プロダクト、今年も続々登場

Babashkaclj-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の主な活動としては以下のようなものがありました:

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

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

データサイエンス領域については、この記事冒頭で紹介したClojure 15周年のイベントでRich Hickeyも「Clojureのデータサイエンス領域での活用に期待している」とコメントしていたり、ClerkPortalData 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!

おわりに

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

  1. 厳密にはseqableを生成する関数

  2. テーブルのベンチマーク結果は https://insideclojure.org/2022/06/15/partitioning/ から抜粋

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

  4. すべてのプロダクトを網羅したリストではありません。

  5. メッセージのログにアクセスできなくなる問題に対しては、ボットを常駐させてZulipに転送してアーカイブを取ったり、独自にログを集積したりする取り組みは行われていました

19
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
5