投稿者「mod_poppo」のアーカイブ

趣味のOSS開発を細く長く続けていくために心がけたいこと

私は趣味でいくつかの自作OSSを開発しています。あまりバズったりはしていませんが(GitHubスターが一番多いのがLunarMLの200超)、それなりの期間続けているつもりです。

この記事では、趣味のOSS開発を細く長く続けていくためのコツを考察します。

自動化する

普段かかる手間は少なければ少ないほど良いです。つまり、自動化です。自動化するための手間はかける価値があります。

テストは自動化している人が多いでしょう。GitHub Actions等でpushの度に動かしましょう。巨大IT企業の金でテストを回しましょう。

(マイナーなOSやアーキテクチャー、それからGPU等に依存するソフトウェアのテストはGitHub Actionsでは回せないかもしれません。そんな場合でもできるだけの自動化はしたいものです。)

バージョン番号の更新も、手で書き換えるのではなく、シェルスクリプト一発でできるようにすると良さそうです。

リリースもGitHub Actions等で自動化できそうです。私はまだそこまではやっていませんが、リリース用のtarballを作るのは自動化しています。make archive でできるようにしたり、ですね。

依存先の更新情報が自動で入ってくるようにする

自分の書いたプログラムが依存しているソフトウェア(言語処理系や依存ライブラリー)がメジャーアップデートされた場合、自分のプログラムも追従したいかもしれません。なので、これらの更新情報が自動的に流れ込んでくる仕組みを作りたいです。

具体的には、GitHubなら当該プロジェクトの通知設定をいじってリリースの情報が流れてくるようにする、もっと古典的なプロジェクトならメーリングリストに登録する、あるいはRSSを購読する、などです。

作業手順を文書化する

久しぶりに触ったら手順を全部忘れていた、なんてことはありがちです。なので、手順をドキュメント化しましょう。README.mdに書いたり、CONTRIBUTING.mdに書いたり、Makefileなら make とか make help でターゲット一覧が出てくるようにしたり、です。

定期的にリリースするために

思い立って自分のプログラムに何か変更を加えたとします。そのとき、あなたはすぐに新リリースを出すタイプでしょうか?そういうタイプならそれで良いですが、私のような怠惰な人間は「後でまた変更を加えたくなるかもしれない、そうすると今リリースせずに少し様子を見た方が良い」と考えがちです。そうすると永遠に新リリースが出ないことになります(忘却するので)。

忘却を防ぐためには、カレンダーに登録しておくのが良さそうです。登録した日が来たら自分のプロジェクトに何か変更が入っているか確認し、変更があれば適宜リリースを行います。変更があってもなくても、次回の日付をカレンダーに登録します。

インターバルは、こまめな人なら1ヶ月、長くて1年くらいでしょうか。私はとりあえず3ヶ月でカレンダーに登録しています。


私がやっている趣味OSSは個人開発なので、チームを組んで開発する場合や、人気が出て大量にissueやPRが立った場合の処理などは別のノウハウが必要かもしれません。

ラズパイにSquidでキャッシュサーバーを立てる

現在の自宅ではマンションの無料インターネット回線を使っていて、公称100Mbpsである。実測で90Mbps程度は出るようだが、決して早い部類ではない。工事して個別に光回線を引き込めばいいのかもしれないが、初期費用もかかるし月額料金もかかる。その前に現状でできることはやっておきたい。

回線が細いと何が辛いかというと、同じ(巨大な)gitリポジトリーをcloneしたり、でかいバイナリー(GHCとか)を何回も落としてくるのがしんどい。こういうダウンロードするものを何とかして手元にキャッシュできないか。

家にはラズパイやNASが転がっている。これらは常時稼働しているので(最近はNASは止めているが)、これらにキャッシュサーバーを立てるのが良いだろう。

QNAPのNASの一部機種はProxy Serverに対応しているようだが、残念ながらうちにあるやつは非対応だ。それに、どっちみちHTTPSには(オフィシャルには)対応していない。

ということで、ラズパイにプロキシーサーバーを立てることにする。

今使っているラズパイはRaspberry Pi 4の8GBモデル、ストレージはUSB接続のSSD(256GBくらい)だ。OSはUbuntu 22.04を入れている。今回の記事でラズパイらしさは(最後に書く件以外は)ないので、Ubuntuなら同様にできるだろう。

続きを読む

TOMLパーサーを書いた/設定ファイルについて思うこと

TOMLについて

プログラムの設定ファイル、あるいはプロジェクトファイルとしては、さまざまなファイル形式が使われてきました。古くはINI、近年はJSONやYAMLなどです。最近よく見かけるのがTOMLです。Rustの Cargo.toml やPythonの pyproject.toml などで使われています。仕様は

で参照できます。TOMLはTom’s Obvious, Minimal Languageの略ということになっており、ミニマルでわかりやすいことがウリのようです。

私がStandard MLで書くプログラムの設定ファイルにもTOMLを採用するかもしれません。しかし、Standard ML向けの既存のTOMLパーサーはまだなさそうです。なので、実装してみました。

続きを読む

コピーしたファイル群のハッシュ値を比較する

ファイル群をコピーした際に、正常にコピーされたか確かめたいことがある。そういう場合に使えるコマンドをメモしておく。

まず、find コマンドで再帰的に shasum を実行する。結果はファイルに保存しつつ、標準出力にも出す(進行状況をわかりやすくするため)。

$ find . -exec shasum -a 256 '{}' \; | tee file-list.txt

コピー元とコピー先で同様のコマンドを実行し、それぞれハッシュ値一覧のファイルを作る。

find の列挙順はよくわからない(Macの find コマンドは -s で辞書順にソートできるようだ)。コピー元とコピー先で列挙の順番が違うかもしれないので、sort したい。shasum の出力は先にハッシュ値が来て、後にファイル名が来る。sort コマンドに -k オプションを使うと、ファイル名の部分でソートできる。

$ sort -k2 < file-list-src.txt > file-list-src-sorted.txt
$ sort -k2 < file-list-dst.txt > file-list-dst-sorted.txt

あとは diff で比較する。

$ diff file-list-src-sorted.txt file-list-dst-sorted.txt

シェルによっては、ソートと diff をワンライナーで

$ diff <(sort -k2 file-list-src.txt) <(sort -k2 file-list-dst.txt)

と書けるかもしれない(bashとzshではできそう)。

diff で比較する以外に、shasum -c でチェックする方法もあるかもしれない。その辺は好みで。

LunarMLの構文をイケイケにするために

Standard MLとLunarMLの関係について、前にこういう記事を書きました。

この時は割と互換性重視でしたが、しかし、クソリプおじさんからの批判に耐えるにはもう少し抜本的な改革が必要そうです。非互換性を厭わずに構文を変えるならどういう構文にしたら良いでしょうか。

(まあクソリプおじさんが前の記事を読んでいたかは定かではないのですが。人が百も承知なことに対して上から目線でご高説を垂れるからクソリプなのです。釈迦に説法という言葉もありますね。)

続きを読む

LunarML: LuaやJavaScriptを出力するStandard MLコンパイラー

本日、私がこの数年開発しているStandard MLコンパイラー「LunarML」の最初のバージョン(v0.1.0)をリリースしました。というわけで、この記事ではLunarMLを紹介します。

はじめに

型のないプログラミング言語で大きなソフトウェアを作るのは辛いです。しかし、動作環境の都合で型のない言語を使わざるを得ない状況が世の中にはあります。この状況を改善できるのが、静的型のある言語で書かれたプログラムを型のない言語のコードへ変換するコンパイラーです。トランスパイラーと呼ばれることもあります。

WebではJavaScriptしか使えない状況が長く続いたため、JavaScriptへコンパイルする処理系は多数登場しました。しかし、それ以外のスクリプト言語、例えばLuaを出力するコンパイラーはまだ少ないように思います。そこで、静的型のある言語からLuaへ変換できるコンパイラーを新しく作ることにしました。

入力とする言語については、新しく作るのではなく、既存の言語を利用することにしました。私はML系の言語が好きなのでML系の言語をいくつか検討した結果、Standard MLを選びました。Standard MLは以下の特徴を持った言語です:

  • 強力な型推論
  • 正格評価
  • カプセル化とコードの再利用を可能にするモジュールシステム
  • 標準(The Definition)と、それに準拠した複数の処理系

機能

LunarMLはSML ’97の(モジュールシステムを含む)全ての機能と、Successor MLのいくつかの機能を実装しています。独自の拡張機能もいくつか実装しています。

標準ライブラリーはまだ不完全ですが、LunarML自身をコンパイルできる程度には揃っています。

複数ファイルからなるプロジェクトのために、MLtonやMLKitと互換性のあるML Basis systemを実装しています。

もちろん、LuaやJavaScriptの世界とのやりとりもできます。

いくつかのバックエンドでは、限定継続も利用できます。限定継続は非同期プログラミングに有用です。

ビルドとインストール

LunarMLは以下のGitHubリポジトリーで開発されています:

ビルドするにはMLtonとLuaが必要です。

$ git clone https://github.com/minoki/LunarML.git
$ cd LunarML
$ make
$ bin/lunarml compile example/hello.sml
$ lua example/hello.lua
Hello world!

インストールには make install を使います。デフォルトでは /usr/local にインストールされますが、PREFIX 変数を指定してインストール先を変更することもできます。

$ make install PREFIX=/opt/lunarml
$ export PATH=/opt/lunarml/bin:$PATH
$ lunarml --help

自分でビルドする代わりに、コンパイル済みスクリプト、もしくはDockerイメージを利用することもできます。コンパイル済みスクリプトはリリースされたtarballに含まれます。

コンパイル済みスクリプトをNode.jsで実行する場合の手順は次のようになります。

$ curl -LO https://github.com/minoki/LunarML/releases/download/v0.1.0/lunarml-0.1.0.tar.gz
$ tar xf lunarml-0.1.0.tar.gz
$ cd lunarml-0.1.0
$ make install-precompiled-node PREFIX=/opt/lunarml
$ export PATH=/opt/lunarml/bin:$PATH
$ lunarml compile example/hello.sml
$ lua example/hello.lua
Hello world!

スクリプトにコンパイルしたLunarMLはとても遅いので気をつけてください。実用の際はMLtonでネイティブコンパイルしたものを利用することをお勧めします。

Dockerイメージを利用する場合は次の手順になります。

$ docker pull ghcr.io/minoki/lunarml:latest
$ docker run --rm --platform linux/amd64 -v "$(pwd)":/work -w /work ghcr.io/minoki/lunarml:latest lunarml compile example/hello.sml
$ lua example/hello.lua
Hello world!

簡単なコードをコンパイルしてみる

Standard MLのHello worldは次のようになります:

print "Hello world!\n";

Luaにコンパイルしてみましょう。実行にはLua 5.3またはLua 5.4が必要です。

$ lunarml compile hello.sml
$ lua hello.lua
Hello world!

JavaScriptにコンパイルして、Node.jsで動かすこともできます:

$ lunarml compile --nodejs-cps hello.sml
$ node hello.mjs
Hello world!

フィボナッチ数を計算する(遅い)コードは以下のようになります:

fun fib 0 = 0
  | fib 1 = 1
  | fib n = fib (n - 1) + fib (n - 2);
print ("fib 10 = " ^ Int.toString (fib 10) ^ "\n");
$ lunarml compile --lua fib10.sml
$ lua fib10.lua
fib 10 = 55
$ lunarml compile --nodejs fib10.sml
$ node fib10.mjs
fib 10 = 55

標準で多倍長整数も使えます。Luaがターゲットの場合は自前の実装を、JavaScriptがターゲットの場合は BigInt を使います。

fun fact 0 : IntInf.int = 1
  | fact n = n * fact (n - 1);
print ("50! = " ^ IntInf.toString (fact 50) ^ "\n");
$ lunarml compile --lua fact50.sml
$ lua fact50.lua
50! = 30414093201713378043612608166064768844377641568960512000000000000
$ lunarml compile --nodejs fact50.sml
$ node fact50.mjs
50! = 30414093201713378043612608166064768844377641568960512000000000000

HaMLetをコンパイルしてみる

HaMLetという、Standard MLのリファレンス実装を謳う処理系があります。これをLunarMLで動かしてみましょう。

$ git clone https://github.com/rossberg/hamlet.git
$ cd hamlet/
$ make hamlet.mlb SYSTEM=mlton
$ lunarml compile --lua-continuations hamlet.mlb
$ lua hamlet.lua
HaMLet 2.0.0 - To Be Or Not To Be Standard ML
[loading standard basis library]
- 1 + 1;
val it = 2 : int
- OS.Process.exit OS.Process.success : unit;

もちろん、JavaScriptへコンパイルすることもできます。

$ lunarml compile --nodejs-cps hamlet.mlb
$ node hamlet.mjs
HaMLet 2.0.0 - To Be Or Not To Be Standard ML
[loading standard basis library]
- "Hello " ^ "world!"; 
val it = "Hello world!" : string
- OS.Process.exit OS.Process.success : unit;

Luaコードの生成

LunarMLはデフォルトではLua 5.3/5.4向けのコードを出力します。--lua オプションで明示的に指定することもできます。

--luajit オプションにより、LuaJIT向けコードを出力することもできます。

Luaの機能はLuaモジュールのAPIを介して呼び出すことができます。現状はあまり使いやすいものではないので、将来的にはもっと便利な方法を導入するかもしれません。

--lib オプションにより、Luaモジュールを生成することもできます。export という名前の変数またはモジュールで定義した内容がエクスポートされます。

出力されたコードは人間可読というわけではありません。人間可読なコードを生成するのは目標には入っていませんが、それでもデバッグの観点からもう少し読みやすいコードを生成したいです。

JavaScriptコードの生成

LunarMLはJavaScriptコードを出力することもできます。現状は実行にNode.jsが必要で、ブラウザーでは動きません。--nodejs または --nodejs-cps オプションを利用します。

Node.jsのAPIは多くが非同期的です。一方、Standard MLの入出力関数は同期的です。この違いを吸収するため、LunarMLは --nodejs-cps オプションが利用された場合はプログラムに対してCPS変換と呼ばれる処理を行っています。--nodejs オプションが指定された場合はCPS変換を行わず、代わりに入出力関数を制限しています。

JavaScriptの機能はJavaScriptモジュールのAPIを介して呼び出すことができます。これも、将来的にはもっと便利な方法を導入するかもしれません。

--lib オプションにより、ESモジュールを生成することもできます。export という名前の変数またはモジュールで定義した内容がエクスポートされます。

LunarMLの標準の文字列型 string はJavaScriptの世界では Uint8Array を使って表現されます。JavaScriptの文字列はLunarMLでは WideString.string 型として利用できます。

今後の計画

LunarMLはまだまだ未完成で、今後実装したい機能はたくさんあります。いくつか挙げます。

  • 標準ライブラリーの充実
  • Successor MLの機能をより多く実装する
  • REPLとインタープリター
  • Online compiler
  • バックエンドの追加
    • ブラウザー向けJavaScript
    • PHP
    • WebAssembly with GC
  • パッケージ管理システム

最後に、GitHubのリポジトリにスターを頂けると嬉しいです:

「型システム入門」の先へ:TypeScriptの型システムのいくつかの側面

この記事は TypeScript Advent Calendar 2023 の8日目の記事です。言語実装勢にも役立つ内容を含んでいるかもしれないので、 言語実装 Advent Calendar 2023 にも登録しています。


TypeScriptで型システムに興味を持った人が「型システム入門」を読んだという話を時々聞きます。「型システム入門」は、Types and Programming Languages (TAPL) という本の邦訳で、型システムに興味を持った人が読むのは自然なことです。

ですが、この本の原著は2002年出版で、最近の話題がカバーされていなかったり、邦題に「入門」とあるように発展的な話題は扱っていなかったりします。一応続編的な感じのAdvanced Topics in Types and Programming Languages (ATTAPL) という本はありますが、これも2004年なので最近の話題はカバーされていません。

そして、TypeScriptの型システムは最近の型の理論/技術をちょいちょい使っています。

そういうわけで、私としては、TypeScriptで型システムに興味を持った人が辿り着くのが「型システム入門」止まりで本当に良いのか、という気持ちがあります。「型システム入門」の先を見たくはありませんか?

残念ながら私は型理論の専門家ではなく、高度な型システムの理論的な説明はとても書けません。なので、この記事ではTypeScriptの型システムのいくつかの側面を紹介し、文献へのポインターを提示することにします。

なお、私はTypeScriptの実装は読んでいません。あくまでドキュメントや挙動を見て判断しています。

続きを読む

GHCへの私の貢献2023

この記事は Haskell Advent Calendar 2023 の6日目の記事です。


私はここ数年、HaskellコンパイラーであるGHCに貢献しています。この記事では、今年(2023年)に私が行った貢献を紹介します。

GHCの開発は独自ホストされたGitLab上で行われています。

続きを読む