Janet for Mortals(2023)
(ianthehenry.com)- Janet for Mortals は Janet プログラミング言語を扱う無料のオンライン書籍だが、この振り返りの焦点は書籍そのものよりも、Web REPL やサンプルプロジェクトまで含めた技術書制作のプロセスにある
- 全体の作業には 20週間 かかり、純粋な執筆の見積もりだった12週間を超えて、Webサイト、C++ バインディング、ブラウザデモ、CLI パーサー、テストツールといった付随作業が加わった
- 書籍サイトは CodeMirror と Lezer の文法を活用した組み込み REPL を提供し、自動補完は WebAssembly で Janet 環境を参照し、
(report)は SQLite にフィードバックを保存する cmd、judge、to do、Toodle.Studio は、Janet が スクリプティング、CLI ツール、テスト、ブラウザ実行、JavaScript 相互運用に使えることを示す基盤的な事例として使われている- 公開直後、Hacker News と Lobsters への投稿だけで、金曜日に30,025人、土曜日に9,568人、日曜日に3,777人のユニーク訪問者があったが、5章以上読んだユニーク IP は 387件 と推定される
本の範囲と実際の作業量
- Janet for Mortals は Janet プログラミング言語を扱う無料のオンライン書籍
- 制作全体の期間は、余暇時間ベースで 20週間 だった
- 当初の見積もりは12週間で、英語本文の執筆時間だけを見れば大きく外れてはいなかった
- 実際のスケジュールには、書籍の周辺にあるコーディングプロジェクトが大きく含まれていた
- 最終的な分量は、コード断片を除いて 英語の散文 44,000語
- 比較対象として挙げられている The Great Gatsby は 47,000語
- 以前の Nix 関連記事シリーズの半分以下の長さ
- 5か月のあいだに、書籍と並行して次の作業も進められた
Webサイトと組み込み REPL
- Janet for Mortals サイトの中核となる仕掛けは 組み込み REPL
- ユーザーはいつでも Escape キーで REPL を開ける
- REPL はページ下部に固定されており、本文を大きく隠さない
- エディタには CodeMirror を使用
- CodeMirror は標準では Janet をサポートしていない
- 以前の Bauble の作業時に作った Janet 言語サポートをベースにした
- Bauble DSL では重要でなかった
multi`backtick`quoted文字列のような Janet 構文も、本では必要だった
- CodeMirror が使う Lezer 文法を強化し、その同じ文法を書籍内コード断片の シンタックスハイライト にも再利用した
- 既知の静的サイトジェネレーターで Janet コード強調をサポートするものは見つからなかった
- 最初は本を白黒にしていたが、見慣れない Janet 構文をより親しみやすく見せるため色を追加した
- 静的サイトジェネレーターは
redoを使って自作した- 生成器の中核は JavaScript で書かれており、Lezer 文法を組み込めた
- 全体構成は shell でつないだ脆い作りだった
- Remark は、本の原稿パースとクライアント REPL の両方で使われた
- コードブロックにラベルを付ける簡単な拡張を追加した
- Janet 標準ライブラリの docstring は Markdown で書かれているため、REPL の自動補完でその場で HTML レンダリングされる
自動補完と一方向フィードバック
- REPL の自動補完は、Janet を初めて使う読者のために重要視された
- REPL 起動時に WebAssembly を通じて Janet 環境を動的に参照する
- ユーザーが docstring 付きの新しいシンボルを定義しても、自動補完結果には現れない
- コマンド実行後のたびに自動補完を再生成することもできるが、それほどの価値はないと判断した
(report)関数は文字列を受け取り、簡単な Web サーバーに POST し、サーバーはそれを SQLite データベースに保存する- コメントボックスに似ているが、REPL から直接送る方式なのが特徴
- この機能でフィードバックを読み、いくつもの誤りを修正した
- 返信手段は実装しなかった
- report バックエンドは Janet ではなく小さな Haskell アプリケーション
- POST リクエストを受けて SQLite データベースに入れる役割だけを担う
- Web サーバーは Janet では作っていない
- Web サーバーでもっとも重要な要素はセキュリティだと考えている
- Janet と Janet HTTP ライブラリがインターネットに直接つなぐのに十分な検証を経ているとは見なしていない
jimmy: C++ 相互運用の実験
jimmyは immer に対する Janet バインディング- immer は永続データ構造ライブラリ
- バインディングは未完成で、実際に必要になるまでは完成させない可能性が高い
- それでも、Janet で C++ コードと相互運用 する方法を示す例としては成功だったと見ている
Toodle.Studio とブラウザ内の Janet
- Bauble は以前に作った小さなアート用プレイグラウンドで、ブラウザに Janet を埋め込んだ最初の経験だった
- 当時は Janet 埋め込みに関する資料があまりなかった
- ブラウザで WebAssembly、Emscripten、TypeScript を併用する必要があり、難度が高かった
- Bauble の最終成果は、アプリケーションロジックが Janet で書かれた Web サイトだった
- UI には JavaScript が多く使われていたが、中核ロジックは Janet が担っていた
- ブラウザで Janet を使う技術をもっと身近にしたいという動機が、本を書く重要な理由の一つだった
- 本では Bauble の代わりに Toodle.Studio を例として作った
- Bauble は状態を持たず、文字列を文字列に変える純関数に近かったため、相互運用の観点では単純だった
- Toodle.Studio は Bauble の明確なフォークだが、JavaScript と Janet の間の相互運用はより複雑
- Toodle.Studio には、本の例としてより適した技術的要素があった
- 長時間動作する Janet プログラムを時間にわたって非同期に実行する必要がある
- JavaScript コードが同じ Janet 値を複数の参照として保持するため、メモリ管理 を考慮しなければならない
- 複雑にネストしたデータ構造を C++ を経由して Janet とやり取りしなければならない
- Toodle.Studio のロゴは Bauble で作ったアニメーションするカメ
- turtle graphics デモなのにカメがいないというフィードバックを受けて追加した
- 目がマウスを追うようにした
cmd: Janet 向け CLI 引数パーサー
cmdはコマンドライン引数パースライブラリ- 本では3段落ほどしか登場しないが、Janet が優れたスクリプティング言語であることを明確に示すために必要だった
cmdは OCaml のCore.Commandから強い影響を受けているCore.Commandは、使ったことのあるコマンドライン引数パースライブラリの中で最高だったと考えている- Janet でその体験を再現しようとした
cmdはCore.Commandほど良くはない- 型があればすべてがずっと簡単になると考えている
- それでも重視する機能の 95% は備えており、簡潔な記法のおかげで ad-hoc スクリプトではより使いやすいと見ている
- まだ Bash 自動補完関数の生成機能はない
Core.Commandはこれを自動生成するcmdAPI は、将来この機能を追加できるよう設計されている
judge: インラインスナップショットテストフレームワーク
judgeは 2021 年に Janet で最初に作ったものの一つ- Janet をより深く理解したあとで ゼロから書き直した
- API はより使いやすくなった
- 実装もはるかに単純で変更しやすくなった
- Judge v1 と v2 の主な違いは、テストの置き場所とヘルパー
- テストは
test/ディレクトリだけでなく通常のソースファイル内にも置ける test-macroとtest-stdoutヘルパーを追加した
- テストは
test-stdoutの OCaml における対応手法 は、職業上テストを書くときにほぼ唯一使う方法だという- OCaml には任意データをソースコードに埋め込む適当な方法がなく、すべてを文字列化する方向で対処している
- 本の公開後、Judge に
--interactiveモードを追加した- 本には Judge でテストとデバッグを扱う章 がある
to do: Bash の代わりに Janet で作った ToDo ツール
to doは スクリプティング章 の例として選ばれた- 以前 Bash で作っていた、そこそこ複雑なツールで、Bash 実装は苦痛だった
- Bash で特に難しかったのは、テキストパースと日付処理
- Sed で複数行テキストをパースするのは楽しい作業ではなかった
- macOS と Linux の両方で同じように動く
date操作は事実上不可能だと見ている
- Janet では PEG、
sh、cmdを活用して、元の Bash ToDo リストより短時間でより多くの機能を作れた- 例として
fzfの複数選択機能を追加した - Bash で null-terminated 文字列を構成するのは難しいと考えている
- 例として
- 本では簡略版だけを扱っている
- 将来の作業予約機能はない
- タスクを「スキップする」概念はない
- 中核機能は、リストへの項目追加と完了処理
zsh-autoquoterと組み合わせると、意外と便利なアプリだと見ている
執筆中のコード作業と保守
- 本の制作 20 週間のうち、コード作業はほぼ半分近くを占めた
- 8週間 はコード作業で、一部期間では執筆とコーディングを同時に進めていた
- 当初は Judge の改善と引数パーサー作成だけを想定していた
- 引数パーサーの実装は予想よりはるかに難しかった
- Bauble があまりに単純だったため、新しいアート用プレイグラウンドをまた作ることになるとは予想していなかった
- 実際の執筆は Sublime Text で Markdown を書いて行った
- Sublime Text は長い散文を書くのに好みのエディタ
- 執筆中に Janet の新バージョンが 2回 出た
- デバッグ章とネイティブモジュール章を言語変更に合わせて更新する必要があった
- 最新の Janet リリースに合わせて本を維持していく予定
公開後の反応とどれだけ読まれたか
- 本の宣伝は、Hacker News への投稿、Lobsters への投稿、ツイート程度だった
- Hacker News では一日中フロントページに載り、しばらく 1 位を維持した
- Nginx アクセスログに基づくユニーク訪問者は次の通り
- 金曜日: 30,025人
- 土曜日: 9,568人
- 日曜日: 3,777人
- この数字はリンクをクリックした人の数に近く、実際に本を読んだ人はずっと少ないと考えている
- サイトにはクライアント側の行動追跡分析はない
- アクセスログだけで大まかに推定している
- 章ごとの訪問の流れを見ると、継続率はあまり良くないようだった
- 第1章はホームページ訪問者の 22%
- 第2章は第1章訪問者の 20%
- 第3章は第2章訪問者の 69%
- 第4章「Pegular Expressions」と第9章「Xenofunctions」は、第3章より訪問者が多かった
- 公開週末終了時点で、実際の読者数についての最良の推定値は 387人
- 5つ以上の異なる章を読み込んだユニーク IP 数を基準にした
名前の由来とフィードバックで見えた限界
- Janet 言語の名前は The Good Place の不死の存在 Janet に由来する
- この存在は死後の世界を移動する mortals を助けるため、本の題名ともつながっている
- もっとも訪問が少なかった章は「Testing and Debugging」だった
- 本の最後から3番目の章
- 最後の3章が本の中でもっとも面白いと考えている
- その章のテスト手法は、自身のエンジニアとしての経歴の中でも最大級の生産性向上の一つだったという
- 組み込み REPL の report 機能で 494件 のフィードバックを受け取った
- 多くは「良い本」やテスト用メッセージだった
- 数十件は誤字報告、説明の要望、有用なコメントだった
- 一部のユーザーは連絡先なしで質問を送ってきたが、返信機能がないため応答できなかった
- report 機能は非常に原始的な一方向フィードバック機能として実装された
1件のコメント
Hacker News のコメント
同じ作者の https://bauble.studio/ も外せない
私の古いコンピュータのブラウザでは動かないが、図書館で勉強していて休憩したいときは、図書館のコンピュータで bauble を30分ほどいじって遊んだりしている
数字をあれこれ変えるだけで作れるグラフィックがすごく、Janet が長く続いてほしいと思う
unknown symbol twistのコンパイルエラーが出るerror: script:16:1: compile error: unknown symbol twistin evaluate [lib/evaluator.janet] on line 81, column 7in bauble-evaluator/evaluate [lib/init.janet] on line 8, column 12Janet を GNU Guile と比較してみるのは興味深い
Guile の本来の強みは、C と並べて使いやすいように作られた言語である点だが、古い言語なので、言語ツールチェーンにパッケージマネージャーを標準で含めるような現代的な感覚はまだ取り入れられていないようだ
Guile で気に入っているのは、RNRS 標準との互換性を説明することに多くの努力を払っている点
Janet が実際にどれほど Scheme らしいのかは分からないが、気になる
誰かが Lua の上に Clojure を作ったものに近い
本は https://janet.guide/ で、言語は https://janet-lang.org/ にある
この本の 実用的なアプローチ がよく、いま Everybody Codes を Janet で解きながら学んでいる大きな理由になっている
https://everybody.codes
「カメはメタファー」という表現は残念
カメはメタファーではなく実物だった: http://classicacorn.computinghistory.org.uk/8bit_focus/logo/...
もちろん「別のものを表す何か、象徴」というメタファーの定義には合っているが、フロッピーディスクの保存アイコンを冷蔵庫だと誤解したという冗談のように聞こえる
昔は実際に楽しくてかわいい ハードウェアのカメ があった、という文脈が抜けている感じがする
タイトルだけ見て、The Good Place の Janet に由来するまた別の AI アシスタントかと思ったが、実際にはプログラミング言語で、うれしい驚きだった
Janet 言語は、死後の世界を進む人間を助ける The Good Place の不滅の存在 Janet にちなんで名付けられており、だからタイトルもそれに合わせた気の利いた冗談になっている
標準の正規表現エンジンの代わりに PEG(解析表現文法) を同梱している点も意外だった
標準の正規表現ライブラリを提供する言語なら、ちゃんとした PEG も一緒に入れてほしいと常々思っていた
<https://www.gnu.org/software/guile/manual/html_node/PEG-Pars...> を参照
sporkモジュールは一種の libc のような位置づけで、正規表現のサブモジュールもあるので、インストールされている可能性が高いJanet で PEG に初めて触れたが、強力で使いやすい点が気に入っている
正規表現は20年以上少しずつ触ってきたのに、PEG は数か月でよりよく理解できるようになった
Janet コミュニティ内には The Good Place 関連のコールバックも多く、複数のサードパーティモジュールが登場人物の名前にちなんで名付けられている
elm/regexとelm/parserは組み込みではないが、elm/regexは代わりに elm/parser を使うよう勧めている名前の由来は気に入っているが、Janet という名前の言語なら、制御を優先する義務もありそうだ
本は読んだし、かなり興味深く遊び心のある感じで、Janet の学習 がずっと楽しくなった
ただし言語ドキュメントはいろいろな面で不足していた
関数やコマンドがただ列挙されているだけで、例や説明がなかったり、何をするのかの説明が抜けていたりすることがよくあった
本で見た機能をドキュメントで見つけられないことも多く、それが Janet を好きでいながら学ぶのをやめた理由の一つになった
常識的なデフォルトが多く、古い Lisp の慣習にこだわるより主流言語に似た点があるのはよい
結局 cons リストではないので実際には Lisp ではないが、それは問題ない
問題は、言語が十分に文書化されていないことだ
Janet コミュニティである程度活動しているので、足りない部分が分かれば ドキュメント改善 に貢献できる
しばしば関数シグネチャだけがあり、追加説明がまったくなかった
広範なドキュメントを作るのに時間と労力がかかるのは分かるが、こういうことがあまりに頻繁で、手を引いて バイナリにコンパイルされる別の Lisp を探し始めた
Janet にはうまく馴染めなかったが、自分の問題なのかは分からない
長く使ったことのある唯一の別の Lisp は Emacs Lisp だが、Janet は十分似ていながら微妙に違うため、ずっと細かなミスをしてしまう
PEG 機能はドキュメント上では魅力的だったが、構文が扱いづらく混乱した
ドキュメントはすべての関数説明があるという意味では完全だったが、例や有用な使用情報が不足していて非常に簡潔だった
JSON パースのような基本機能も含まれていないようだった
すばやくスクリプトを書くための言語で JSON や XML パーサーを先に書かなければならないなら、その時点で失敗した作業だ
4年ほど前のことなので、エコシステムは良くなっているかもしれないし、スクリプト用途として Janet を評価した基準自体が間違っていたのかもしれない
それでもこの記事を見ると、もう一度インストールしてみたくなる
sporkモジュールに入れてあり、これは一種の libc のような位置づけだコアを軽く保とうとする内部志向が強い
多くのプロジェクトが
sporkに依存しているので、初期段階でインストールされている可能性が高いドキュメントは悪くない方だが、実用例にはまだ穴があるという点には同意する
この部分は https://janetdocs.com が補っており、各関数ごとに「コミュニティ例」へリンクしている
話題からは外れるが、原文の別のブログ記事のおかげで 分割型メカニカルキーボード を使うようになった
数年間、肩と手首の痛みがあったが助けになったし、Ian の他の記事も見てみることを強く勧める
いつでも
Escを押して下部に張り付く 内蔵 REPL を開く方式は、よい判断ではないかもしれないブラウザにはすでに
Escのデフォルトショートカットがあり、Firefox はEscを押すと全画面表示から抜ける