😎

Squintで(主にVimと)遊ぼう

2023/12/09に公開

TL;DR

  • 時間がなくてお遊びまでで実用性はないです。
    • 駆け足で書いているので不備などあったら申し訳ないです。
  • Squint を使うとClojureScriptライクな言語でVimプラグインが書けます。
    • VSCodeとかもいける?わからないけれど
    • Squint はまだ Work In Progress なので、今後破壊的な変更が入る可能性があります。

対象

  • JSの代わりにClojure(Script)が書きたい
  • ClojureScript は好きだけれどもJSの出力結果としていろいろ埋め込まれて巨大になるのは好きじゃない

ClojureScript とは

そもそも Clojure とは JVM 上で動作する Lisp 方言の1つで、ClojureScript は JavaScript をターゲットとした Clojure コードのコンパイラです。

Clojure は主に *.clj という拡張子を、ClojureScript は主に *.cljs という拡張子を使いますが、どちらにも対応した *.cljc という拡張子のコードを用意することで、バックエンド/フロントエンドで同じ Clojure のコードを共用できるという特徴があります。

https://clojure.org/guides/reader_conditionals

Squint とは

Squint は ClojureScript と同じように Lisp のコードを JavaScript にコンパイルしてくれるツールです。
ただ ClojureScript の置き換えを目的とはしておらず、バンドルサイズなどにおいてより軽量なものを使いたい人向けのツールになっています。

ClojureScriptとの一番の違いはSquintではJSに元から組み込まれているデータ構造のみを使ったJSを出力する点です。
それによりバンドルサイズの削減してClojureScriptと比べて軽量なJSを出力してくれます。

Squint を試す

Squint は npm で公開されているので npm, yarn, pnpm などでインストール可能ですが、今回は諸事情で Deno から使います。

REPL

以下のコマンドで REPL が起動できます。

$ deno run -A npm:squint-cljs repl
user=> (+ 1 2 3)
6

実行

以下のような ClojureScript ファイルが用意されているとします。

(ns example)

(defn sum [& args]
  (apply + args))

(println (sum 1 2 3 4 5))

実行は run サブコマンドを使います。

$ deno run -A npm:squint-cljs run example.cljs
[squint] Running example.cljs
15

JSへのコンパイル

JSへのコンパイルは compile サブコマンドになり、実行することで example.mjs ファイルが出力されます。

$ deno run -A npm:squint-cljs compile example.cljs
[squint] Compiling CLJS file: example.cljs
[squint] Wrote file: /private/tmp/foo/example.mjs

中身は以下のようになっています。

import * as squint_core from 'squint-cljs/core.js';
var sum = (function () {
 let f1 = (function (var_args) {
let args21 = [];
let len__25503__auto__2 = arguments.length;
let i33 = 0;
while(true){
if ((i33 < len__25503__auto__2)) {
args21.push((arguments[i33]));
let G__4 = (i33 + 1);
i33 = G__4;
continue;
};break;
}
;
let argseq__25786__auto__5 = ((0 < args21.length)) ? (args21.slice(0)) : (null);
return f1.cljs$core$IFn$_invoke$arity$variadic(argseq__25786__auto__5)
});
f1.cljs$core$IFn$_invoke$arity$variadic = (function (args) {
return squint_core.apply(squint_core._PLUS_, args)
});
f1.cljs$lang$maxFixedArity = 0;
f1.cljs$lang$applyTo = (function (seq4) {
let self__25597__auto__6 = this;
return self__25597__auto__6.cljs$core$IFn$_invoke$arity$variadic(squint_core.seq(seq4))
});
return f1
})();
squint_core.println(sum(1, 2, 3, 4, 5));

export { sum }

最初に import している squint-cljs/core.js は ClojureScript で提供されている標準ライブラリの一部が実装されたものです。
これも現状では2000行程度なので、全部合わせてもさほど大きくはないですね。
https://github.com/squint-cljs/squint/blob/main/src/squint/core.js

Vim と遊ぶ

こんな感じで実行やコンパイルができるSquintですが、折角JSにコンパイルできるのでいろいろと遊んでみましょう。
JSにコンパイルできるということはDenoで実行ができるということ、Denoが使えるということはDenopsが使えると考えるのは全人類共通かと思います。
そう Vim です。Squint で Vim プラグインを書いてみましょう!

しかしこれは Clojure Advent Calendar

Vim の話を長々と書くつもりはありません。子どもも風邪をひいていてゆっくり記事を書いている時間もありません。

ひとまずは完成品のデモをリポジトリとして用意しました。
https://github.com/liquidz/vim-deno-squint-demo

軽く解説すると src 配下が ClojureScript で書かれたコードで、リポジトリルートで make するとコンパイルされたJSが denops ディレクトリ配下に出力されます。
1点ユニークなのが ns フォームで、以下のように require でURLを指定しています。これは Deno を使っているがために許される書き方なので ClojureScript からするとかなり特殊に見えますね。

https://github.com/liquidz/vim-deno-squint-demo/blob/8353fad7ce9266279bfec3796da1398be2ede228/src/helloworld/main.cljs#L1-L3

またコンパイル時の注意ですが、先程言及した squint-cljs/core.js は Deno での実行時には存在しないため、実行できる形に変換が必要です。
Deno では npm モジュールをそのまま import することができるので、以下のように npm:squint-cljs/core.js のように変換することで Deno にて実行が可能になります。

https://github.com/liquidz/vim-deno-squint-demo/blob/8353fad7ce9266279bfec3796da1398be2ede228/Makefile#L4

実用に至っていいない点

上記のリポジトリで Vim プラグインとして動くものは出来はしているのですが、以下の理由から実用性はまだまだ低いです。

  • REPL駆動開発ができない
    • Squint は nREPL にも対応しているので、nREPLに接続できるClojure開発環境であれば簡単なREPL駆動開発は可能です
    • ただし特に Deno を使った場合は外部ライブラリなどが正しく評価できないために、今回使った Denops などもREPL上で動作を確認しながらコードを書くということはできません
  • SquintのnREPLはまだ不完全(?)

最後に

後半駆け足気味でしたが、Squintはまだまだ開発中のツールではあるものの応用できる範囲は広そうです。
私はついVimと遊んでしまいましたが、公式リポジトリ上READMEにもあるようにブラウザゲームなどにも使えるので是非みなさんもいろいろ遊んでみていただけたらと思います!

https://github.com/squint-cljs/squint#playground

Discussion