世界を動かす技術を、日本語で。

ジャネット: 軽量で表現力豊かな現代リスプ

概要

Janet はシステムスクリプトや他プログラムへの組み込みに適した動的言語。 C99準拠 で実装され、主要OSで動作し、移植性が高い。 スレッド・ネットワーク・イベントループ 内蔵など、他の埋め込み言語より多機能。 REPLやビルドツール、豊富な標準ライブラリを提供。 エディタや外部ライブラリ連携 も充実し、学習や導入が容易。

Janetの主な用途

  • システムスクリプト言語 としての利用
  • 他プログラムへの組み込み 用途
  • プロトタイピングや動的システム開発 での活用
  • C99 で実装されており、 Windows・Linux・macOS で動作
  • 標準Cでない機能(動的ライブラリロード等)は 単純実装、移植性重視
  • 埋め込みやすさ多機能性 の両立
    • スレッド、ネットワーク、イベントループ、サブプロセス管理、PEG(正規表現ライク)など内蔵

Janetの特徴

  • 最小構成 :1バイナリのみで即利用可能
  • スレッド・ネットワーク・イベントループ の標準サポート
  • クロージャ・ガーベジコレクション 対応
  • グリーンスレッド(継続) のファーストクラスサポート
  • 配列/タプル・ハッシュテーブル・文字列 の可変/不変両対応
  • マクロ・末尾再帰最適化 サポート
  • Cとの直接連携 (抽象型・C関数呼び出し)
  • 動的Cライブラリロード
  • 字句スコープ・REPL・インタラクティブデバッガ
  • PEG(Parsing Expression Grammars) を標準搭載
  • 500以上の関数・マクロ がコアライブラリに収録
  • jpm によるスタンドアロン実行ファイル生成
  • janet.c/janet.h をプロジェクトに追加するだけで導入可能

コード例

(defn sum3 "Solve the 3SUM problem in O(n^2) time."
  [s]
  (def tab @{})
  (def solutions @{})
  (def len (length s))
  (for k 0 len (put tab (s k) k))
  (for i 0 len
    (for j 0 len
      (def k (get tab (- 0 (s i) (s j))))
      (when (and k (not= k i) (not= k j) (not= i j))
        (put solutions {i true j true k true} true))))
  (map keys (keys solutions)))
(let [arr @[2 4 1 3 8 7 -3 -1 12 -5 -8]]
  (printf "3sum of %j: " arr)
  (printf "%j" (sum3 arr)))
  • 簡単なHello World 例もサポート
    • (print "hello, world!")

Janetの使い方

  • REPL起動 :janetバイナリを引数なしで実行
  • ヘルプ表示-hフラグ指定
  • スクリプト実行janet myscript.janet
  • REPLでドキュメント表示(doc)コマンド
  • コマンド例
    • (+ 1 2 3)6
    • (print "Hello, World!")Hello, World!
    • (os/exit) → REPL終了

主なコマンドラインオプション

  • -h:ヘルプ表示
  • -v:バージョン表示
  • -e code:Janetコードを直接実行
  • -l lib:モジュールを事前にインポート
  • -c source output:ソースをイメージにコンパイル
  • その他、デバッグ・警告・エラー制御など多数

モジュール・ライブラリ

  • GitHubに多数の補助プロジェクト
  • jpm install で簡単インストール
    • Circlet:HTTPサーバ
    • Joy Web Framework:Web開発用フレームワーク
    • JSON:JSONパーサ・エンコーダ
    • SQLite3:データベースバインディング
    • WebView:HTML+CSS UIの生成
    • Jaylib:Raylibバインディング(2D/3Dゲーム開発)
    • JHydro:暗号化ライブラリ
    • JanetUI:libuiバインディング

エディタサポート

  • Neovim・Vim・VSCode・Emacs・Sublime Text・Kakoune・TIC-80・Helix など主要エディタ対応
  • Zulip でエディタ・ツール議論エリアあり

コミュニティ・ドキュメント

  • Zulip Instance (https://janet.zulipchat.com)で活発な議論
  • GitHub Discussions もサポート(Zulipの方が機能豊富)
  • Janet Docs で公式ドキュメント&ユーザー事例を共有
    • 誰でも事例追加可能、学習支援

Janet は、 埋め込みやすさ高機能性 を両立した動的言語。 主要OS対応・導入容易・豊富な標準機能・活発なコミュニティ で、 スクリプト・組み込み・プロトタイピング など幅広い用途に最適。

Hackerたちの意見

ジャネット独自の何かってある?ちょっと見ただけだけど、ほとんどがちょっと違う構文と「モダン」な標準ライブラリを持ったスキームみたいに見える。なんで自分の好きなスキーム(ガイル)からジャネットに乗り換える必要があるの?

全然スキームじゃないよ!結局、コンスセルもないし。クロージャーっぽい感じ(マップが至る所に、コレクションAPI、不変データ構造)で、1MBの実行ファイルが10MB未満のRAMで動くんだ。ファイバーはすごく面白いし、エラーハンドリングにも使われてる。PEGについてはまだ理解できてないけど。

ガイルについてはあんまり知らないけど、ジャネットは開発がかなり楽だったよ。バイナリも小さくて、raylibやいくつかの小さなCライブラリを簡単にラップして埋め込めた。これで配布がすごく楽になる。自分のシンプルな2Dゲームのjaylib(raylib)コードはこんな感じ。ls -laoh build/app -rwxr-xr-x 1 worthless 2.8M 27 Jul 17:28 build/app otool -L ./build/app ./build/app: /usr/lib/libSystem.B.dylib (互換性バージョン 1.0.0, 現在のバージョン 1356.0.0) /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (互換性バージョン 1.0.0, 現在のバージョン 24.0.0) /System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo (互換性バージョン 1.2.0, 現在のバージョン 706.41.0) /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (互換性バージョン 1.0.0, 現在のバージョン 275.0.0) /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (互換性バージョン 1.0.0, 現在のバージョン 1.0.0) /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (互換性バージョン 45.0.0, 現在のバージョン 2674.3.0) /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (互換性バージョン 150.0.0, 現在のバージョン 4034.0.0) /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics (互換性バージョン 64.0.0, 現在のバージョン 1951.0.4) /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (互換性バージョン 1.0.0, 現在のバージョン 1226.0.0) /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (互換性バージョン 300.0.0, 現在のバージョン 4034.0.0) /usr/lib/libobjc.A.dylib (互換性バージョン 1.0.0, 現在のバージョン 228.0.0) これらはほとんどのOSXマシンに標準的にあると思うし、Linuxシステムでも似たような感じ。LLMはジャネットを扱えないみたいで、クロージャーだと思い込んで色々とおかしくなっちゃう。

ジャネットはスクリプトに使うことが多いかな。特に外部とやり取りする必要があるスクリプトには、起動が速くて標準ライブラリが充実してるから(特にJSONの操作やHTTPSリクエスト、PEGでのパース、マップにデータを保存するのに便利)。大きなプロジェクトでは、モジュール性やパフォーマンス、メタプログラミングが重要になるからガイルを使うことが多いけど。とはいえ、最近は両方にクロージャーを使ってるよ(スクリプトを実行するのにbabashkaを使ってる:https://babashka.org/)。

2023年のアドベント・オブ・コードのほとんどをジャネットでやったけど、すごく良い経験だった。PEGをできるだけ使うようにして、オーバーキルな時もあったけど、他のパーサーや正規表現に比べて読みやすさや使いやすさが気に入った。依存関係がない小さな言語であるのもいいね。どこにでもインストールしてるし、携帯のtermuxにも入れてる。スクリプトにぴったり。ネイティブなクロージャーのことを夢見てたけど、ジャネットはそれに近い。すべてを持ってるわけじゃないけど、サイズや依存関係のコストがずっと低い。シンプルで使いやすく、RPi Zeroでもちゃんと動く。

大きな違いは、ジャネットのプログラムを追加の依存関係やランタイムなしで実行ファイルにコンパイルできること。これで配布がすごく楽になる。FFIもかなり簡単だし。

Guileが気に入ってるなら、Janetに乗り換える価値はないかもね(でも速いけど)。ただ、絶対にSchemeではないよ。もっと言うと、ネイティブコンパイルされたスクリプト最適化されたClojureみたいな感じ。

Janetのいいところは、Schemeと比べて、似てるけど互換性のない言語のファミリーじゃなくて、一つの言語だってことだね。

ネイティブコンパイラや型はあるの?基本的に、SBCLのパフォーマンスに近づけるのかな?Common Lispよりもクリーンなものがあって、画像サイズもずっと小さいといいな。もしそれで decent なパフォーマンスもあれば、買いだね。

これはCommon Lispの代わりにはならないよ。ガイルと同じニッチにいるけど、かなり速い。

Janetはjpmを使ってスタンドアロンの実行可能ファイルにコンパイルできるよ。

Janetは多くの動的言語よりも速いバイトコードVMを使ってるけど、SBCLのネイティブコンパイルのパフォーマンスには及ばないね。ドキュメンテーション用のオプショナルな型アノテーションはあるけど、最適化用ではないよ。

これはLuaに似た軽量な埋め込み可能なインタープリターとランタイムなんだ。動的で強い型付けだけど、ネイティブコンパイルはないよ。Cライブラリを呼び出したり、Cプロジェクトに埋め込むのも簡単。テストした感じ、似たようなタスクではPythonの約2倍の速さだよ。スクリプトやリソースをランタイムと一緒にバンドルした実行可能ファイルに埋め込むことで、「コンパイル」できるんだ。

Janetは素晴らしいけど、お願いだからツール周りをもっと頑張ってほしい。俺が知ってる限り、REPLを使ってインタラクティブに作業したりデバッグしたりするためのIDEがほとんどないし、前にEmacsで試したときは専用モードがほとんどなかったよ。

俺はEmacsでjanet tsモードとAjsc netrepl統合を使ってるけど、もうしばらくこれでやってるよ。もう一回試してみたら?

Janetはneovimのconjureでもサポートされてるよ。https://github.com/Olical/conjure それ用のLSPは結構いい感じで動く。

他の人が作業してるって言ってたから、今の私のEmacs設定はこれだよ:

(use-package janet-ts-mode
  :ensure t
  :defer t
  :vc (:url "https://github.com/sogaiu/janet-ts-mode" :rev :newest))
(use-package ajrepl
  :ensure t
  :defer t
  :vc (:url "https://github.com/sogaiu/ajrepl" :rev :newest)
  :hook (janet-ts-mode . ajrepl-interaction-mode))

それと、毎回文法を聞かないようにtreesit-autoの修正も:

(use-package treesit-auto
  :pin melpa
  :ensure t
  :custom (treesit-auto-install 'prompt)
  :config
  (add-to-list 'treesit-load-name-override-list '(janet "libtree-sitter-janet-simple" "tree_sitter_janet_simple"))
  (add-to-list 'treesit-language-source-alist '(janet-simple . ("https://github.com/sogaiu/tree-sitter-janet-simple")))
  (treesit-auto-add-to-auto-mode-alist 'all)
  (global-treesit-auto-mode))

何かツールを作ることに貢献してみた?

nvim-paredit [0]にJanetサポートも追加したし、それとconjure [1]、nvim-parinferを組み合わせて、neovimでめっちゃスムーズな編集体験を得てるよ。 [0]: https://github.com/julienvincent/nvim-paredit [1]: https://github.com/julienvincent/nvim-paredit [2]: https://github.com/olical/conjure

Janetの良い入門書については、https://janet.guide/を見てみて。

この本を見るのは初めてだ。著者は信頼できる人だね!プログラミングの本にちょうどいいユーモアのセンスを持ってる。

https://en.wikipedia.org/wiki/3SUM

それは解に対応するインデックスを出力してるんだ。例えば、@[6 1 7]はA[6], A[1], A[7]に対応してて、それぞれ-3, 4, -1だよ。@[2 5 10]は1, 7, -8に対応してる。

もしJで42の解を1行のコードで書くなら:

_ 11 11 #:,I. 0=,a+/,+/~ a=: 2 4 1 3 8 7 _3 _1 12 _5 _8

2x12の行列で8つの解を出すなら:

2 12 $, ~. (/:~)"1 ({&a) _ 11 11 #:,I. 0=,a+/,+/~ a=: 2 4 1 3 8 7 _3 _1 12 _5 _8 _3 1 2 _5 2 3 _1 _1 2 _8 4 4 _5 1 4 _3 _1 4 _8 1 7 _5 _3 8

前の議論はこちら: https://news.ycombinator.com/item?id=23164614 https://news.ycombinator.com/item?id=34843306 https://news.ycombinator.com/item?id=28255116 https://news.ycombinator.com/item?id=28850861

こういう言語を見ると、エコシステムがどれだけ進んでるのか気になるよね。GNU Guileをたくさん使ってきたけど、ライブラリも結構あって、ほとんど何でもできるんだ。ウェブ関連も出てきてるけど、まだ完全には整ってないかも。Racketもライブラリが豊富で、標準ライブラリのウェブフレームワークもあるし。じゃあ、次のウェブプロジェクトをJanetで始めたいとしたら、Schemeを知ってるから、TCOがあって、Schemeとほぼ同じように書けるなら、すぐにでも新しいLispを書き始められると思う。ただ、funcallを使わなきゃいけないのはちょっと面倒だけどね。Janetにはウェブプロジェクトを支えるライブラリ、例えばウェブサーバーやSXMLみたいなのがあるのかな?それともJSONパーサーみたいなものはある?こういう小さなものがあると、基本を開発する手間が省けて、実際のプロジェクトに集中できるんだけど。データ構造についてはどう?機能的なデータ構造はあるのかな?GNU Guileには少なくともメンテされてないguile-pfdsがあるけど、いつか機能的なデータ構造についてもっと理解して、もっと作ったり、そのライブラリを維持したりできるようになりたい。でも、学習リソースは少ないんだ。AVL木の機能的なバージョンを見つけるのも難しいし!多くの場所では、非永続的で非機能的なバージョンのデータ構造しか教えてなくて、それを翻訳するのは簡単じゃないし、データ構造の作り方をよく知っていれば、パフォーマンスに無駄な影響を与えることもある。再現性も大事だよね!GNU GuileではGuixに必要なものが揃ってるから最高なんだけど、他の言語では、再現性や決定性を重視してない古いパッケージ管理のアプローチに頼らなきゃいけないかもしれないし、ロックファイルやチェックサムのファイルを作る能力すら欠けてることもある。再現性のない世界には戻りたくないな。静的型付けのバリアント、例えばCarpにも目をつけてるけど、同じ疑問が浮かぶよ。いくつかは本当に素晴らしいみたいで、使うのが楽しそうなんだけど、エコシステムが整ってないと知ったら残念だな。基本的なツールを自分で作らなきゃいけないのは、時には楽しいけど、疲れることもあるし、実際のプロジェクトに取り組む時間がなくなっちゃうこともあるから。

Janetには約10個のウェブフレームワーク(半分はSSG、半分は動的)と多くのウェブサーバーがあるよ。Joy [0]が最も機能が充実したスタックアプローチで、Janetdocs [1]がそれを使ってる。でも、君が言ったことは全て標準ライブラリに含まれてるよ。ここに、拡張された標準ライブラリ(sporkと呼ばれる)を使ったcertbotが10行であるよ: https://codeberg.org/veqq/janetdocs/src/branch/master/ssl-fe... json/decodeは自動的にJSONをネイティブデータ構造にマッピングするんだ。パッケージマネージャーも含まれていて、今まで見つけた全てのコードを再現可能にビルドできるよ。データ構造は配列だけで、マップ、配列、文字列があって、全て可変か不変だよ。関数型データ構造を実装する意図はないと思うけど、ライブラリとして「簡単に」作れるかもね。 - [0] https://github.com/joy-framework/joy - [1] http://janetdocs.org/

ここにいくつかの永続データ構造の実装があるよ:https://github.com/ianthehenry/jimmy ただし、これは未完成で、主にC++との相互運用の例として使われてる。

Lispはめっちゃクールだよね。でも、1つだけ気に入らないのは、構文スタイル(fun a b c)が、IDEのオートコンプリートの面では、あまり対称的でない「OO」スタイルa.fun(b, c)に負けちゃうところ。私にとって、IDEのヘルプはスムーズな開発体験を提供して、無駄な脳のエネルギーを最小限に抑えるために重要なんだ。編集とオートコンプリートをもっと良くする方法を見つけたいな。IDE(そして多分私たちの脳)の働き方は、「ここにXがある、じゃあ何ができるかな?」って感じだと思う。それはエディタで「X.[カーソルここ]」と入力することに繋がって、IDEがXで「できる」ことを見たり検索したりできるようになる。でも、Lispで「([カーソルここ]」って書くと、「その関数のシグネチャは何だっけ?」ってすぐに直面することになるけど、IDEは何も手がかりがないんだ。引数を入力してからオートコンプリートをトリガーするような別の編集スタイルがあればいいのに。

Odinの言語サーバーでは「フェイクメソッド」って呼んでるよ。 https://odin-lang.org https://github.com/DanielGavin/ols

Haskellにはこれに似た機能があるよ。(_ a b c)って入力すると、コンパイラーがその3つの変数に基づいてどのメソッドが適切か教えてくれるから、ある意味もっと便利だね。

他のLispについては分からないけど、Clojureには名前空間があって、それで十分だよ。だから、"(foobar/"って入力すると、foobar名前空間内の関数のリストが出てくるんだ。

Janetは他のLispと同じように、矢印マクロを持っていて、「OO」スタイルで書けるんだよ。(-> a (fun b c))

Clojureでは、これは単に(a/fun b c)か(. a fun b c)になるよ。aが名前空間かオブジェクトかによって変わるんだ。オートコンプリートは素晴らしく機能するよ。Cursiveを使ってコーディングセッションを試してみることを強く勧めるよ。そうすれば、古いLISPの意見を客観的にリファクタリングできるから。

「a.」がうまくいかない場合、つまり「a.fun(b, c)」じゃなくて「b.fun(a, c)」や「c.fun(a, b)」の場合はどうする?それに、「var x = new」から「var x = new ByteArenaFactory()」に自動補完されるのも、何もないところから「ByteArenaFactory」に行くのと同じように、「(fun a b c)」に行く感じだよね。

これは自動補完の必要性よりも、慣れや強制的な型システムの影響が大きいと思う。a.aのメソッドに自動補完されるのと同じように、(fun funの型シグネチャを満たすスコープ内の変数のリストに自動補完されるかもしれない。ただ、Lispの多くは型付けされてないし、型をサポートしているバリエーションでもそうだね。OOスタイルにも慣れが影響してると思うけど、特に自然だとは思わないな、これは主観的だけど。自分がやりたいことは大体分かってるけど、そのメソッドを持ってるオブジェクトを見つける必要がある。例えば、特定のヘッダーを読みたいと思っても、それがRequestにあるのか、Request.Headersにあるのか、ヘッダーが別のオブジェクトとして渡されるのか分からない。(get-header "SOME-HEADER"みたいにして、IDEが(get 'headers request)みたいなのを渡す必要があるって教えてくれる方がスッキリする。

「どのLispを使うべきか?」のための決定木みたいなのってあるのかな?CL、Scheme、Racket、Clojure、Janet… Elispは少なくとも分かりやすいね :)

パフォーマンスと「Lispらしさ」を求めるならCL。再帰が好きならScheme。JVMが必要だったり就職したいならClojure(これが俺)。DSLの魅力にハマってるならRacket。LispyなLuaならFennel(Janetの著者と同じ)。LispyなCが欲しいならJanet。

CLはLisp界のC++みたいなもんだね。標準化されてて、マルチパラダイムで、機能が豊富で速いし、大規模なコードベースを意識して作られてる。ただ、古い標準だから、ネットワークやスレッドみたいな期待される機能が欠けてることもある。実装は通常これらを提供していて、互換性ライブラリが実装の違いをうまく埋めてくれる。チームがいて、大きなプロジェクトでスピードが必要ならCLを選ぶべき。Schemeは教育目的で書かれたもので、すごくミニマルだし、実装は仕様やSRFI(オプション機能の仕様)以外ではあまり互換性がない。ただ、GuileやChickenみたいな主要な実装を選べば、たくさんのライブラリがあって小規模から中規模のプロジェクトには使いやすい。スピードはあまり知られてないけど。コンピュータサイエンティスト的な考え方をするならSchemeを選んでみて。ほかの言語はあまり使ったことがないけど、Clojureは関数型言語だから遊ぶのが楽しかった。JavaやJavaScriptに縛られてなければもっとやってたかも。Racketも学ぼうと思ってるけど、時間がないんだよね。個人的には、普段の仕事がLispベースじゃないから、テキストをいじるためにelispを使うことが多い。elispはCLと同じ伝統から来てるから、かなり似てるよ。

Unix用のCとWindows用のC、マイコン用のCなんかと同じくらい違うよね。結局、どれも同じ言語だし、一番の違いは使えるライブラリだよ。好きなLispを選んで、さあ始めよう! :) GNUシステムを使ってるなら、GNU GuileでLispを学ぶのがめっちゃ良かったよ。特にMITのSICPの講義(Hal AblesonとGerry Sussmanのやつ)を受けるときにね。選択肢はたくさんあるけど!

いい感じだね、クリーンな構文が好きだし、特にdefnがきれい。ちゃんとした関数を実装する前に興味を失っちゃったから、俺のは実行できる変数しかないけど、興味があればここにあるよ:http://dusted.dk/pages/slispRepl/ でも、めちゃくちゃで、ただのインタープリタだよ。