1 ポイント 投稿者 GN⁺ 2 시간 전 | 2件のコメント | WhatsAppで共有
  • Janet は小さな Lisp 方言だが、命令型言語・第一級関数・単一識別子名前空間・レキシカルなブロックスコープを備えており、シンプルに始められる
  • コア言語do, def, var, set, if, while, break, fn の8つの命令だけで構成され、マクロがより強力または便利な制御フローラッパーを作ってくれる
  • 配布性 は、Janet プログラムを Janet ランタイムと静的リンクしたネイティブ実行ファイルにコンパイルし、利用者に Janet や依存関係のインストールを求めない形で確保される
  • Parsing Expression Grammar (PEG) は正規表現より単純で強力かつ予測可能で、sh はパイプやリダイレクトを Janet 内で表現できるようにして CLI 作成の幅を広げる
  • コンパイル時実行 はトップレベル命令を先に実行した後、プログラム状態のスナップショットをディスクに保存することで、マクロなしでも実行時へ値・共有参照・ジェネレーター・クロージャ状態を渡せるようにする

シンプルなコア

  • Janet は命令型言語であり、第一級関数、単一識別子名前空間、レキシカルなブロックスコープを持つ
  • 言語コアは do, def, var, set, if, while, break, fn の8つの命令で小さく保たれている
  • マクロによって、より強力または便利な高水準の制御フローラッパーが可能になる
  • ランタイムの意味論はなじみやすく、標準ライブラリ 全体が1ページに収まるほど、言語の残りの部分も小さい

ネイティブ配布と埋め込み

  • Janet プログラムは、Janet ランタイムを静的リンクしたネイティブ実行ファイルへ簡単にコンパイルできる
  • 配布を受ける利用者は、Janet、プロジェクト依存関係、そのほかの追加コンポーネントをインストールする必要がない
  • Janet は自身をバイトコードへコンパイルした後、そのバイトコードを Janet ランタイムを起動する .c ファイル内に書き込み、システムの C コンパイラでその C ファイルをコンパイルする
  • シンプルな “hello world” ネイティブバイナリは 1MB より小さく、Janet 1.27.0 の aarch64 macOS では 784K だった
  • このバイナリには Janet ランタイム全体、ガベージコレクタ、バイトコードコンパイラまで含まれるため、実行時に Janet コードを評価するプログラムも作れる
  • Janet ランタイムは小さな C ライブラリなので、リンクした後に通常の C 関数を呼び出して Janet の値を操作できる
  • Web サイトにも埋め込め、Toodle のようにユーザー定義のプログラマブル DSL を持つ静的サイトを作れる

テキスト解析とサブプロセス DSL

  • Janet のテキスト処理は正規表現ではなく Parsing Expression Grammar を基盤としている
  • Parsing Expression Grammar は正規表現より単純で強力かつ予測可能で、行単位に縛られないため複数行テキストも解析できる
  • HTML、JSON、そのほかの非正規言語を解析でき、任意の null バイトを含むバイナリファイル形式も扱える
  • sh は、パイプやリダイレクトを Janet コード内で直接表現できるサードパーティ製シェルスクリプティング DSL である
($ find . -name *.janet | say)
  • この DSL は Janet を、Perl の妥当な代替から、かなり広い範囲のプログラムに対する Bash の妥当な代替へと押し上げる

コレクションと構文感覚

  • Janet のコレクション型には可変と不変の両方の形がある
  • 不変コレクションは値セマンティクスを持つため、不変ベクタ [1 2] はメモリアドレスが異なっても (take 2 [1 2 3]) と区別されない
  • 可変コレクションは参照セマンティクスを持つため、ハッシュテーブル @{:x 1 :y 2} は自分自身とのみ等しく、同じキーと値を持つ別のハッシュテーブルは別オブジェクトである
  • 構文では括弧を広く使うが、リストには []、テーブルには {} を使って形を分けている
  • 可変リテラルには @"mutable string" のように常に @ 接頭辞を付ける
  • 匿名関数は (fn [x] (+ 1 x)) と書き、|(+ 1 $) のように | で式を関数へ持ち上げる短縮記法もある
  • スプラットまたはスプレッドは ; を使って (+ ;args) のように表す
  • バッククォート文字列は任意の数のバッククォートで開き、同じ数で閉じられ、バッククォート文字列内では \n のようなエスケープシーケンスは適用されない
  • 残余引数は . ではなく & を使って (defn foo [first & rest] ...) のように書く
  • Janet は reader macro をサポートしないため構文自体が固定されており、Janet を読めればすべての Janet プログラムを読める

マクロとコンパイル時状態

  • Janet マクロはコードを書くコードであり、コンパイル時に値と抽象構文木を操作する現在の実行フローと、将来実行されるアプリケーションコードのフローを同時に扱うことになる
  • Janet マクロは hygienic ではなく、関数用の別名前空間もない
  • ただしリテラル関数を unquote できるため、完全に参照透過なマクロを書ける
  • Janet プログラムをコンパイルすると、トップレベル命令、通常の文、関数宣言などを先に実行した後、プログラム状態のスナップショットをディスクへ記録する
  • このスナップショットは共有参照を保持するため、再起動後も可変値を継続して変更できる
  • ジェネレーターは次に再開したときに実行する命令を記憶し、クロージャも閉包した値を保持する
  • マクロはコンパイル時コード実行の特殊な形だが、マクロなしでもこの能力を使える
  • ゲームではスプラインを事前処理でき、コンパイル時にファイルを読んで最終バイナリへアセットを入れられ、任意の副作用も実行できる
  • Janet for Mortals は SQL スキーマファイルを基にデータベースバインディングを自動生成する例を示しており、この種の作業は多くの言語ではかなり難しいと評価している

Lisp 伝統よりも快適さ

  • Janet は古い Lisp の慣習をそのまま踏襲していない
  • CARfirstPROGNdoLAMBDAfnSETQdef と名付けられている
  • nil は空リストではなく独立した型であり、ブール値は第一級の値である
  • EQ, EQL, EQUAL, EQUALP 系を避けており、連結リストもほとんど見かけない

2件のコメント

 
GN⁺ 2 시간 전
Hacker Newsのコメント
  • Janetには物足りない点がある。主にパッケージ管理でのバージョン指定が弱く、高度なHTTPルーティングのようなライブラリ群も不足している。
    それでも、JPMでバイナリやスクリプトを作れて移植性が高いのは本当に気に入っている。以前、概念実証としてPlaydateゲーム機にJanetプログラミング言語を載せてみたこともある。
    Janetでコードを書くのは楽しいが、そのたびに人から自分がこの言語の作者だと思われるのはちょっと困る

    • Julia EvansがJuliaでGunzipを可視化した面白い記事がある: https://jvns.ca/blog/2013/10/24/day-16-gzip-plus-poetry-equa...
      「JanetがJanetを書く」版もやってみると良さそう
    • jeepは試しただろうか: https://github.com/pyrmont/jeep/
      依存関係をベンダリングし、jpmなしでもモダンなJanetバンドルを簡単にインストールできるようにしてくれる
    • サーバー側が必要なら、veqqが言うように独自の軽量HTTPサーバーを実装した joy がある。クライアントが必要なら、Janet C APIでlibcurlや他のHTTPクライアントをラップするのはかなり簡単だ。
      LLM活用に前向きなら、ラッパーはLLMに書かせて、実際のロジックはJanetで書ける
    • 高度なHTTPルーティングが具体的に何を指すのか気になる。Web関連の作業はすべて https://github.com/joy-framework/joy でやっているので、不足機能があるならたぶん1週間以内に追加できると思う
  • 同じ開発者がもっと前に作った似た言語として Fennel もある。Luaにコンパイルされ、実装もすべてLuaで書かれている。
    独自の標準ライブラリがないため、Janetのパーサライブラリのような優れたものが多く欠けているが、Luaを組み込んだ環境でスクリプトを書くには向いている。
    https://fennel-lang.org/

    • Fennelは本当に素晴らしく、Clojure系への入門としても良いやり方だ。最大の不満は、デバッグが典型的なトランスパイル地雷原になっていることだ。
      FennelとLua VMの間の接続は非常に脆く、JanetのデバッガやREPLの品質には到底及ばない。Fennelは移植性がはるかに高く、LuaJITのおかげでSBCLを圧倒できる可能性すらあるだけに残念だ。
      ただ、トランスパイル体験が完全に足を引っ張っていると思う。回避策はあるが、debug.setinfo を実装しても match ブロックのような厄介な境界ケースに出くわす。
      LuaJIT2をforkして、デバッグとエラー構造を言語透過性にもっと合うよう修正することには大きな価値があると思う。そうなればFennelのような言語はずっと魅力的に見えるはずだ
  • 記事の筆者は、以前Hacker Newsでも取り上げられたこれらのツールを Janet で作っている。
    https://bauble.studio
    https://toodle.studio
    この2つの興味深いアートツールのおかげで、しばらくJanetにかなり期待していた

  • Janetが注目されるのを見ると、いつも嬉しくなる。モダンな機能の1つとして sandbox を挙げたい。
    「インタープリタが特定のシステムリソースを利用できないように機能セットを無効化する。いったん無効化した機能を再び有効にする方法はない。」
    https://janet-lang.org/api/misc.html#sandbox

    • 本当にクールな機能だが、平均的なプログラマがこうしたサンドボックス化を必要とする状況がどんなものなのか気になる
  • 「SETQ is def」を見て、最初は思わず声に出して「え?」と言ってしまった。SETQは束縛を作らず、更新しかしないからだ。
    ドキュメント(https://janet-lang.org/docs/bindings.html)を読むと、実際に筆者のほうが間違っていて、「defで作られた束縛は不変」と書かれている。たぶん「SETQ is set」と言いたかったのだろう。
    JanetはGuile、Tcl、CLのちょうど良い中間点のように見えて、本当に好きになりたいのだが、ラムダや制御フロー演算子に角括弧ベクタを使うことに本能的な抵抗がある。Clojureも同様で、どうしても受け入れにくいが、十分に慣れればいけるのかもしれない。
    それと、現在のLSP/SLIMEの状況がどうなっているのかも気になる。最近ではかなり重要だ

    • Clojure構文における角括弧の使い方は非常に一貫していて、かなり論理的だ。
      丸括弧を使うと、リストの先頭要素が残りのリストをどう解釈するかを決める。たとえば (func a b c) は関数実行、(macro x y z) はマクロ展開、([p q r] …) は引数ベクタで始まり、その後に評価式が続く「裸の」関数本体になる。
      角括弧は、要素が同じ「種類」で、先頭要素が特別ではないときに使う。たとえば (defn f [a b c] …) は同種の引数の集まりで、先頭の引数が特別ではないし、(let [a 1 b 2] …) も束縛の集まりで、先頭の束縛が特別ではない。
      思いつく唯一の例外は、case で複数のマッチ要素をまとめる場合だが、これは利便性のためだ。この理屈を理解してから考えが変わり、その後は美しいと感じるようになった
    • 角括弧や波括弧は、単に使わなくてもよい。[1 2 3] の代わりに (array 1 2 3) を使えばいいし、(fn [x] (+ 1 x)) の代わりに (f (x) (+ 1 x)) を使えばいい。
      必須ではない
    • Clojureで分配束縛がどう動くかを理解すると、角括弧が担っている役割がはっきり見えてくる
  • 一定の長さを超えるシステムスクリプトでは、私にとって Janet が sh, Python, awk などの代わりになっている
    スクリプトの起動時間が非常に速く、私のシステムでは hyperfine で 1.4ms であり、dash の 1ms に近い。コンパイル済み実行ファイルではなく、スクリプトとしての話だ
    sh-dsl モジュールのおかげで、($ cmda w x | cmdb y z) のようにシェルコマンドをとてもエレガントに書ける。デバッグのためにイメージをロードできる機能も大いに役立つ
    ごく最近使い始めたばかりだが、すでに一番好きな言語の一つになりそうで、これまで使った他の Lisp は SICP 用の MIT Scheme だけだ

    • 私にとっては babashka が sh, Python, awk などの代わりになってくれた
  • この記事は新鮮だ。インターネットの AI 以前の議論 の匂いが残っている
    新しい言語、新しい文法、何年もコードを書いてきた人たちの激しい議論がある。誰か AI が許可されないオンラインコミュニティを始めてくれたらいいのにと思う

    • 最近の流れを追っていなかったなら、digg.com の最新リローンチは ボットの猛攻 に耐えられず失敗した
      実際にはその一つ前のリローンチと言うべきかもしれず、今はまた新しいホームページがあるようだ。オンラインコミュニティで AI を安定して防ぐ方法を最初に見つけた人は、おそらく大金持ちになる可能性が高い
      https://www.techspot.com/news/111698-digg-relaunch-fails-two...
    • AI が許可されないオンラインコミュニティをどう作れるか、よく考える。特にオンライン 匿名性 を壊さずに実現する方法が難しい
      ある種の「人間性の証明」は難問だ
    • AI のすごいところは、AI の熱烈な支持者でなくても、AI と無関係な会話に AI を持ち込めてしまうことだ。反対派のほうが代わりにやってくれる
    • そういう場所はたぶん lobsters だろう。300 件を超えるコメントが付いた、おそらくサイト史上最大規模の議論のあとで AI 文章が禁止された
      運営が定めた正確なルールは「意味のある人間の著作」だったが、惑わされてはいけない。lobsters には LLM にイデオロギー的に反対している人が多い。技術がどれだけ「意味のある形で」使われたかはあまり重要ではない
      私の作業は AI が触れたというだけでゴミ扱いされ、AI を使っていると話したとき、露出狂やフェティシスト呼ばわりする人までいた。参加を考えている人には前もって伝えておきたい
    • 皮肉にも、最上位コメントであるこのコメントが今や AI についての内容になっている
  • 「リテラル関数をアンコートできるようにして、Janet は完全に参照透過なマクロを書けるようにする」といった文章に、Lisp の人たちは本当に抽象的なものに興奮するようだ
    街の普通の人にこんなことを言ったら、たぶん逃げ出したくなるだろう

    • 参照透過性 のないリテラル入り C マクロの例はこんな感じだ
      #define MULTIPLY(x, y) x * y
      int result = MULTIPLY(2 + 3, 4); // 14
      ある言葉の意味を知らないからといって、それが悪いわけではない。文脈を見ると、たぶんそういう意味で言ったのだと思う
      プログラミングで繰り返し現れるパターンや問題について共有言語を持つのは良いことだ。「あのプログラマー集団は変だ」みたいに用語を揶揄するのは無意味で、逆効果でしかない
    • 街の普通の人に オブジェクト指向プログラミング言語 とその利点を説明したことがあるのか気になる
    • 1年ほど前から Scheme インタプリタを書き始めて、かなり先まで進めた。数か月前に新しい仕事を得てから中断している
      再開しようか考えているが、ニッチな機能が実装する価値のあるものか悩んでいる。私には実装も難しい。dynamic-unwind を飛ばし、もしかすると call/cc も外して、その代わりデバッグ性、エコシステム、性能、パッケージ管理に集中したほうがいいのかもしれない
    • 「お仕事は何をされていますか?」と聞かれるたびに、大体そんな反応をされる
      だから「コンピュータ関係の仕事をしています」くらいにかなり曖昧に言うか、「あまり面白い仕事じゃないんです」と言って話題を変えようとする。もう少し具体的に言うだけで、人は出口を探し始める
    • 平均的なプログラマーでもそうだろう
      正直、Lisp 系コミュニティは小さかったからこそ、かえって得をした面があると思う。たとえば大昔の Design Patterns ですら継承より合成を好むべきだと警告していたのに、オブジェクト指向プログラマーたちは今でも 15 階層の深い階層構造を作っていた
  • Janet に初めて触れたとき、これらのドキュメントが本当に役立った
    https://janetdocs.org/tutorials
    https://janet.guide/ 筆者が作ったものだ

  • HN にたまに上がる Janet の記事に惹かれていたが、みんなが高く評価する Janet for Mortals はまったく mortals 向けの本には感じられなかった

    • もっと穏やかな導入資料もある: https://janetdocs.org/tutorials
    • 意外だ。この言語は非常に直感的で単純で、覚えるべき規則もとても少ない。Lisp ではあるが表面積が非常に小さい
      他の言語と比べても Janet は本当に学びやすい部類なので、その本が難しいというのは驚きだ。本自体は読んでいないが、言語にはある程度慣れていて、正直褒めることしかない
    • 個人的には マクロ構文 が前半に出すぎていてそこで詰まるが、その後には本当に価値のある内容がたくさんある
    • Haskell でもそんな感じを受けた。Haskell は私には難しすぎるが、文法そのものは気に入っている
      Janet は Lisp 2.0 のように見えるので、文法も Lisp 的だ
 
GN⁺ 2 시간 전
Lobste.rs の意見
  • Janet を使い始めて 10か月で、APL 系言語以外はほとんど忘れてしまうほど夢中になり、コミュニティのドキュメントサイトを運営しながらチュートリアルも書いている
    始めて 3 週間以内に個人用スクリプトをすべて書き直し、新しく作る運用ソフトウェアも Janet で書いている
    Janet は実装上、言語全体がほぼ ハッシュマップのように動作するので、(keys (curenv)) でローカルシンボルを、(keys (getproto (curenv))) でコアシンボルを確認できるし、その気になればハッシュマップベースで CLOS に似たものも作れ、実装例もある
    Joy Web Framework で 20 個前後の Web サイトと複数のサービスを 512MB の無料 VPS 1 台で動かしており、関連するチュートリアルも書いている
    ただし「不変コレクション」という表現は実態とはやや異なり、標準ライブラリは概して可変値を返すため、現時点では不変性にこだわる大きな理由はない
    コンパイル時の値を実行時に渡す機能が特に強力だった。たとえば聖書の .tsv をコンパイル時にハッシュマップとしてバイナリへ埋め込んでおけば、実行時は参照だけで済み、embed を使った Go 版より2 倍高速という結果も出た
    Go で同じものを手作業でハッシュマップとして実装するとずっと長くなるが、Janet なら Lisp to Go コンパイラも 46 行で作れた
    Ian Henry が挙げたさらに興味深い点は、Janet が クロージャの状態をイメージ/セッション間で保持することだ。(curenv) ハッシュマップに関連環境を保存しておき、新しい REPL セッションで復元してもクロージャ内部の状態が継続する

    • Janet 製のすばらしい例としては https://bauble.studio があり、制作過程の記事は https://ianthehenry.com/posts/bauble/building-bauble/ にある。WASM の活用が目を引く
      Lisp ベースの音楽 DSL である https://lisp.trane.studio/ もあり、論文 https://dl.acm.org/doi/abs/10.1145/3677996.3678285 と結果例 https://x.com/greg_ash/status/1824218993118388708 も見る価値がある
      自作ライブラリもあり、複数のデータ構造上で SQL ライクなクエリ構文を提供している
      データフレームの挿入・更新・CSV 保存/読み込みをサポートし、Datalog と miniKanren も含み、APL のようなベクトル化演算も可能だ
      Janet 上で J を直接使う jnj もあり、Joy Web Framework には (var account (db/find-by :account :where {:login (auth-result :login)})) のような DB クエリ DSL があり、実際の Web サイトの認証コードでも使われている
    • 原文で「不変コレクション」と書いたのは表現が不正確だった気がする。意図していたのはコレクションというより 複合値型と参照型 に近い
      「不変コレクション」と言うと永続データ構造を連想するが、それは有用だとしても Janet の基本機能ではない
      実際に気に入っているのは値型と参照型の対称性で、記事の最後に「immutable composite values」とは書いてあったものの、自分が書いたのでなければ意味をすぐには汲み取れなかったと思う
  • Janet は新鮮で 埋め込み可能な言語 なので、ゲームエンジンのようなプロジェクトの組み込みスクリプティングに使ってみたい
    DLL を差し替えずに高速反復のためのホットリロードが必要な場面によく合いそうで、Lua も優れているが Janet のほうがある面ではより表現力が高そうだ

  • Janet は言語として本当に魅力的で、いつか Zig プロジェクトの スクリプト言語 として使ってみたい。より多くの人が Janet に言及しているのはうれしい

  • 良さそうではあるが、すでに babashka で Clojure スクリプティングに慣れてきているので、似た印象を受ける。埋め込み可能性 以外に見落としている大きな利点があるのか気になる
    「残りの引数を含む配列の分割代入が潜在的に高コストなコピーを引き起こす」といった点は、全体的にあまり関数型らしくなく見えて好みではない

    • Janet のキラー機能の 1 つは組み込みの PEG サポートだと思う。ゲーム開発のサイドプロジェクト用にテキストボックス DSL を作ったとき、本当に便利だった
      他の言語でテキストをパースするたびに恋しくなる機能だ
    • その種の分割代入はよく使う書き方ではなく、Janet の配列/タプルは連結リストではない
      Janet は小さくて埋め込み可能な Clojure というより、関数型プログラミング支援がより良い Lua に近い