西暦2262年問題に対処するべきか

西暦2038年問題はみなさんご存知ですよね。2038年1月19日午前3時14分7秒(UTC)を過ぎると 世界中のUNIXがばくはつする問題 time_t が符号付き32ビットなプログラムで現在時刻を正しく扱えなくなる問題です。

C言語の time_t は典型的にはUnix epoch(UTCで1970年1月1日午前0時)からの経過時間(うるう秒は考慮しない)を秒単位で保持しており、それが\(2^{31}-1\)に到達するのが2038年1月19日午前3時14分7秒(UTC)なわけですね。

2038年は割と近い将来なので、モダンなC処理系では time_t を64ビット整数にするなどの対応を行なって2038年問題を乗り切ろうとしています。

それでも、時刻を固定長整数で表現する限り、いつか限界が来ます。「time_t を64ビット整数にする」という対応は、問題を西暦2038年から西暦292277026596年に先送りしたに過ぎません。

そして、時刻の表現を「秒単位」ではなくもっと細かい単位にするとこの限界はもっと早くやってきます。この記事では、時刻の表現をどういう刻みで何ビットにすると限界がいつになるのかを検討してみます。

続きを読む

Standard MLのIOとLunarMLのIO

プログラムにとって入出力は大事です。入出力機構がないと、計算の入力を受け取ることも、出力を出すこともできません。

Standard MLにも当然入出力に使う型と関数が定められています。例えば、print 関数は標準出力に文字列を出力し、flushします。TextIOBinIO などのモジュールを使うと、ファイルの読み書きを行うこともできます。

LunarMLも、これらのモジュールを一部実装しています。しかし、今の実装はやっつけなので、もっとしっかりした(準拠度の高い)実装にしたいです。

Standard MLの入出力

Standard MLの入出力はいくつかのレイヤーに分かれています。

一番高いレイヤーが「手続き的入出力」すなわち IMPERATIVE_IOTEXT_IOBinIO です。これらは

  • 手続き的な入力
  • 手続き的な出力
  • ストリームのリダイレクト

などの機能を提供します。

手続き的入出力は、ストリーム入出力のラッパーと思えます。手続き的入出力のストリームの型はストリーム入出力の型を使うと

type instream = StreamIO.instream ref
type outstream = StreamIO.outstream ref

という風に理解できるでしょう。

ストリーム入出力は STREAM_IO で表されます。機能的には、

  • 関数的な(遅延リスト的な)入力
  • 手続き的な出力
  • バッファリング

を提供します。「関数的な入力」というのは、例えば「一文字読み取る」関数が

val input1 : instream -> (elem * instream) option

という型を持ち、「入力に与えられたストリーム」とは別の「一文字読み取った後のストリーム」を返すということです。同じストリームに input1 を複数回適用すると同じ結果が得られることが期待されます。

ストリーム入出力の下にあるのがプリミティブ入出力です。プリミティブ入出力 PRIM_IO は、システムコールを抽象化したものだと思えます。機能的には、

  • バッファリングなしの、手続的な入出力
  • ノンブロッキングIO(オプション)
  • ランダムアクセス(オプション)
  • OSのファイル記述子へのアクセス(オプション)
    • ファイル記述子に対しては、等価性比較、ハッシュ値の取得、大小比較ができることが想定されています。

があります。ただし、プリミティブ入出力はあくまでインターフェースを定めるものであり、特定のシステムコールに紐づいたものではありません。特定のシステムコールを呼び出すプリミティブ入出力の実装を提供するのは openIn とか stdIn とかを提供する側の役目です。

Standard MLにはこのほかに、OSのシステムコールに対応する型と関数が規定されています。

LunarMLの入出力

LunarMLは、スクリプト言語の提供する入出力機能をラップしてStandard MLの型と関数として見せたいです。スクリプト言語の提供する入出力機能とは、Luaで言えば io モジュール、Node.jsで言えば Readable/Writable などのストリームです。

スクリプト言語の提供する入出力機能をシステムコールとみなしてプリミティブ入出力として提供できれば良かったのですが、現実にはそううまくはいきません。スクリプト言語の提供する入出力機能にはバッファリングがあるのに対して、プリミティブ入出力にはバッファリングはありません。具体的には出力ストリームのflush操作がプリミティブ入出力にはないのです。書き込み操作の度にflushすればエミュレートできるかもしれませんが、ストリーム入出力のレイヤーでバッファリングを再実装するのかという問題もあります。

今考えているプランは、ストリーム入出力を単なるプリミティブ入出力のラッパーとするのではなく、内部実装として「プリミティブ入出力、あるいはスクリプト言語の提供する(バッファリングされた)ストリーム」の2択を持てるようにする案です。ストリームからプリミティブ入出力のインターフェースを得る場合は、ファイル記述子っぽいものを含めて、逆の操作(ストリーム入出力の構築)を行うときに「スクリプト言語の提供するストリーム」を復元できるようにします。ファイル記述子としては、スクリプト言語の提供するストリームと独自に割り当てる整数をハッシュテーブルで対応させて管理することにします。

まあ、言葉にするのは簡単ですが(これでも結構考えたのですが)、実装するのは面倒くさいです。少しずつやっていきます。

趣味の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の関係について、前にこういう記事を書きました。

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

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

続きを読む