OCml と Eio のスピンテスト

2026/07/20 16:32

OCml と Eio のスピンテスト

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

著者は、Raft 合意アルゴリズムの実装のための基盤として堅牢な RPC サーバーおよびクライアントを構築することを目的に、Haskell から OCaml に移行している。この変更の動機は、OCaml が関数の純粋性と命令型機能の組み合わせ、最新のマルチコア対応の改善、および Jane Street の OxCaml を中心とするエコシステムにある。並行性については、Eio ライブラリを選択し、その決定論的な実行モデルを評価しており、これは以前に他のフレームワークで遭遇したコンパイラエラーによるテストの難しさに対して優れているとされている。学習の旅路は「Real World OCaml」(RWO) で始まったが、著者は現在 RWO が演習がなく高度なトピック(例:GADT)での理解が難しいため、CS31031 のリソースから始めることを提案している。初期の障壁として Rust の構文への好みが挙げられるものの、直近の目標は並行システムのための基盤を確立することである。今後の主要な技術的課題には、Eio フローの管理が含まれ、特に非同期ファイバをスピンする際にブロックせずにクロージャーを取り扱うことに関する点が挙げられる。このプロジェクトは OCaml が Go ユーザーにとって持つ可能性を示しているものの、その特殊性および並行性の特有の癖により、Jane Street のようなニッチな環境以外での広範な産業採用には制限がある可能性がある。

本文

OCaml と Eio で始める並行 RPC サーバー構築

はじめに:なぜ OCaml なのか

私は以前から関数型プログラミングに興味を持っていましたが、Haskell の複雑さに挫折した経験がありました。一方、OCaml は最近再び注目を集めています。

  • 再興の要因: マルチコアサポートやエフェクト(Effect)の導入により進化。Jane Street が開発する「OxCaml」のようなシステム開発特化アプローチも登場。
  • 並行処理フレームワーク Eio: 決定論的(deterministic)な設計を採用しており、テストが非常に容易な点に魅力を感じました。

学習リソースの選び方

まずは適切な教材を選ぶことが重要です。

『Real World OCaml』

  • Jane Street が開発・維持する標準ライブラリ『Base』を使用している点が特徴です。
  • 注意: 以前は標準ライブラリが貧弱でしたが、現在は徐々に成長しており、実社会での利用も正当化されています。
  • 課題: 後半の GADT や一次階級モジュールといった高度な概念では、演習問題が少なく理解に苦しみました。

『CS3110』コース(推奨)

基礎を固めるにはこちらの教材が最適です。

  • 形式: 動画とテキストの視聴覚教材を提供。
  • 利点: 演習問題が充実しており、基礎概念の定着に役立ちます。
  • 学習順序: まずは『CS3110』で基礎を学び、その後『Real World OCaml』へ移行するのがおすすめです。

『Real World Haskell』との比較

  • プロジェクト指向や特定のライブラリ構成を求める人にとっては、『Real World OCaml』は失望する可能性があります。

コードを書く感覚:How It Feels In The Hand

OCaml でコードを書くのは、関数型と命令型プログラミングの良いバランスが取れているため非常に楽しいです。

再帰と高階関数の選択

リストを反復処理する際、再帰関数と

List.map
(および同様の高階関数)のどちらを使うかを選択できます。

let print_recursive list =
  let rec loop i list =
    match list with
    | [] -> ()
    | x :: xs ->
      Printf.printf "[%d] => %i";
      loop (i+1) xs
  in
  loop 0 list

let print_fn = List.iteri (fun i x -> Printf.printf "[%d] => %i\n" i x)

let () =
  print_recursive ["foo"; "bar"; "baz"];
  print_fn ["foo"; "bar"; "baz"]
(* 
[0] => foo
[1] => bar
[2] => baz
[0] => foo
[1] => bar
[2] => baz
*)

可変変数の活用

必要であれば、

mutable
キーワードで可変変数を利用することも可能です。

type client = { mutable request_id : int }

let dispatch cli ~msg =
  let request_id = cli.request_id in
  cli.request_id <- request_id + 1; (* リクエスト ID を更新 *)
  
  Printf.printf "Dispatching %d/%s to server\n" request_id msg

コンパイラーのエラー処理

OCaml のコンパイラーはエラー発生時にファイル全体を再構築する傾向があり、開発サイクル(記述→エラー修正→再構築)が少し重い場合があります。特に一つのモジュールに集中している場合、効率的な修正が困難になりがちです。

Eio で RPC サーバーを実装

基礎知識

Eio はエフェクトに基づく IO 並行処理ライブラリです。以下の用語を理解しておきましょう。

  • Fiber: OS スレッドではない実行スレッド。
  • Switch: ファイバーをグループ化したもの(Go の
    sync.WaitGroup
    に似る)。
  • Flow: 読み書き可能なデータの流れ。
  • Stream: 有界でスレッドセーフなキュー(Go のチャンネルに似る)。
  • Promise: 非同期計算の結果を表す型。

サーバーの実装

サーバー側では、Flow を直接使うのではなく、

Buf_read
Buf_write
を使用してバッファ付きの読み書きを行います。これにより効率を向上させることができます。

パケット構造

単純なプロトコルとして、リクエスト ID(1 バイト)、名前(文字列)、引数(文字列)をシリアル化します。 例:リクエスト ID 10 で「Hello」を送る場合。

00000000: 0a04 4563 686f 0548 656c 6c6f            ..Echo.Hello

コード例

サーバーは接続を受け付け、フローを読み取ります。

open Utils

type conn = {
  src : Eio.Buf_read.t;
  sink : Eio.Buf_write.t;
}

let rec conn_loop conn =
  let request_id = Eio.Buf_read.uint8 conn.src in
  let procedure_name = read_string conn.src in
  let body = read_string conn.src in
  
  traceln "rx %d/%s(%s)" request_id procedure_name body;

  (* RPC ロジック: Echo や Capitalise など *)
  let res = match procedure_name with
  | "Echo" -> body
  | "Capitalise" -> String.capitalize_ascii body
  | _ -> failwith "unknown procedure"
  in

  traceln "tx %d/%s(%s) => %s" request_id procedure_name body res;
  
  Eio.Buf_write.uint8 conn.sink request_id;
  write_string conn.sink res;

  conn_loop conn

テスト実行

サーバーを起動し、nc(netcat)でテストできます。

Echo プロシージャ

> printf '\x00\x04Echo\x05Hello' | nc localhost 1470 -q0
Hello⏎

Capitalise プロシージャ

> printf '\x01\x0aCapitalise\x07matthew' | nc localhost 1470 -q0
Matthew⏎

クライアント実装:並行処理と安全な呼び出し

単一のプロシージャ呼出しのみを送信するように制御し、並行して動作させるクライアントを作成します。

リクエスト管理

書き込み用ファイバーでは、ストリームからタスクを読み取ります。

type request = { procedure : string; arg : string; }

type t = {
  mutable request_id : int;
  writeq : request Eio.Stream.t; (* リクエストのストリーム *)
}

let rec send_loop cli w =
  let req = Eio.Stream.take cli.writeq in
  let request_id = take_and_inc_request_id cli in
  write_request request;
  send_loop cli w

プロミスとレスポンス処理

サーバーからの応答を返すためには、Promise を使用します。Eio では Promise は

Promise.t
(待機側)と
Promise.u
(解決側)に分かれています。

  • 送信時: リクエストに解決用の Resolver を設定し、ハッシュテーブルに格納。
  • 受信時: 応答を読み取り、対応する Promise を解決。
type waiter = { resolver : string Eio.Promise.u; }

type request = {
  procedure : string;
  arg : string;
  resolver : string Eio.Promise.u;
}

type t = {
  mutable request_id : int;
  writeq : request Eio.Stream.t;
  waiters : (int, waiter) Hashtbl.t;
}

動作確認

複数のファイバーから並行して呼び出しを実行できます。

open Eio.Std

let port = 1470

let () =
  Eio_main.run @@ fun env ->
  Eio.Switch.run @@ fun sw ->
  let net = Eio.Stdenv.net env in

  let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, port) in
  let flow = Eio.Net.connect ~sw net addr in

  let cli = Dumb_rpc.Client.create () in
  
  (* サーバー側ファイバーをバックグラウンドで開始 *)
  Eio.Fiber.fork_daemon ~sw (fun () -> Dumb_rpc.Client.run cli ~flow; `Stop_daemon);
  
  (* クライアント側から並行して呼び出し *)
  for i = 0 to 5 do
    Eio.Fiber.fork ~sw (fun () ->
      let arg = Printf.sprintf "arg-%d" i in
      traceln "Invoking Echo(%s)" arg;
      let res = Dumb_rpc.Client.invoke cli ~procedure:"Echo" ~arg in
      traceln "Got response Echo(%s) => %s" arg res
    )
  done

出力結果の一部:

+Invoking Echo(arg-0)
+Invoking Echo(arg-1)
...
+tx 0/Echo(arg-0)
+rx 0 => arg-0
+Got response Echo(arg-0) => arg-0
...

結論

OCaml は、関数型でありながら命令型も可能という「美味しいスポット」を兼ね備え、高速なコンパイラーと強力なエラーハンドリングを提供しています。ユーザー数はまだ少ないですが、入門への道筋は明確になりつつあります。

Eio は Go を使った経験があれば馴染みやすいですが、非同期処理の概念に慣れない場合、Flow の閉じ方やファイバーの起動タイミングなどで躓くことがあります。近い将来、動作する Raft 合意形成アルゴリズムの実装についての記事に戻る予定です。

同じ日のほかのニュース

一覧に戻る →

2026/07/23 2:30

ターレンス・オのチャートプとの対話:ヤコビアン仮定に対する反例について

## Japanese Translation: 该平台主要通过安全的登录提供个性化体验。经过身份验证后,用户将获得量身定制的响应而非通用回答,并可访问由其保存的聊天历史所告知的答案。关键功能包括直接在界面中创建自定义图像,以及上传外部文件以实现与工作流的无缝集成。通过将创意工具与基于记忆的个性化相结合,该平台将标准效用转化为自适应环境,从而简化任务,并在无需重复设置的情况下利用之前的互动。

2026/07/23 2:20

GigaToken:言語モデルのトークナイゼーション速度が約1,000倍高速化

## Japanese Translation: Gigatoken は、HuggingFace および tiktoken などの標準的なトークナイザーの超高速で即座に置き換え可能なものとして機能し、ネイティブ API を通じてデータ処理を革命化します。これは、マルチスレッド Rust プログラミング、SIMD 最適化、キャッシュ階層の改良、分岐の最小化といった高度なエンジニアリング技術によって達成された画期的成果です。ベンチマーク結果では、EPYC 9565 などの高エンド AMD プロセッサ上において、HuggingFace のトークナイザーよりもほぼ 989 倍、tiktoken よりも 681 倍以上の高速化が確認されています。 本ソフトウェアは、Llama 3/4、Qwen、Gemma、Phi-4、DeepSeek、GLM、Nemotron、Kimi K2、Mistral、CodeLlama などの主要な AI モデルをサポートします。スタンドアローン API(最も高速)および HuggingFace または Tiktoken を既存のワークフローで使用するための互換モード(わずかに低速)を備えています。本ツールは現在、Linux ベースのシステム上で AMD EPYC、Apple M シリーズ、または AMD Ryzen チップを使用する際に最適化されたバイト対ペア符号化を用いて最も効果的に動作し、Windows でのサポートは限られています。ユーザーには WSL の使用を推奨します。 今後の計画としては、Python インテグレーションの改良(現在は ABI3 を使用しており、これは内部 API よりもわずかに低速)およびさらなる最適化によるハードウェア互換性の拡大が含まれます。現在知られている制限事項には、API におけるファイルシンクの欠如と SentencePiece 最適化の制限があります。最終的に、Gigatoken は精度を損なうことなく、モデルトレーニングと推論を大幅に高速化するトークナイゼーション時間を劇的に削減することを約束します。研究用途については、@software{roed2026gigatoken...} として引用されるべきです。

2026/07/23 6:15

Malleable コンピューティング、Emacs、そしてあなた

## Japanese Translation: 著者は、大量導入向けのスケーラビリティよりも速度と簡潔さを優先する軽量のツール `fj` を使用し、Emacs 内で GitHub の Issue 管理を自動化することに成功した。このソリューションは複雑なローカルサーバーを避けるために既存の `gh` コマンドラインユーティリティを利用して認証を行い、UI 操作には Elisp パッケージ `Transient`、フォーマット変換には `ox-gfm` を統合することで、ユーザーがエディタから直接 Issue のメタデータをコピーし、Org モードでコメントを作成し、ブラウザウィンドウを開くことを可能にしている。これらすべての機能は JSON 解析ロジックで約 20 行以内で実装されている。「可変計算(malleable computing)」の原則に従い、Emacs を再起動することなしに迅速なプロトタイピングを可能にしている。2 時間半という初期開発フェーズとコードのリファクタリングを経て、最終製品は個人の利用に最適化された約 400 行の Lisp から構成されている。このアプローチは、伝統的な大衆向けソフトウェア("N")とは対照的に、最小限の計画で堅牢なツールを個人ニーズのために構築することは非常に実現可能であることを示している("1")。 ## Summary: 著者は、大量導入向けのスケーラビリティよりも速度と簡潔さを優先する軽量のツール `fj` を使用し、Emacs 内で GitHub の Issue 管理を自動化することに成功した。このソリューションは複雑なローカルサーバーを避けるために既存の `gh` コマンドラインユーティリティを利用して認証を行い、UI 操作には Elisp パッケージ `Transient`、フォーマット変換には `ox-gfm` を統合することで、ユーザーがエディタから直接 Issue のメタデータをコピーし、Org モードでコメントを作成し、ブラウザウィンドウを開くことを可能にしている。これらすべての機能は JSON 解析ロジックで約 20 行以内で実装されている。「可変計算(malleable computing)」の原則に従い、Emacs を再起動することなしに迅速なプロトタイピングを可能にしている。2 時間半という初期開発フェーズとコードのリファクタリングを経て、最終製品は個人の利用に最適化された約 400 行の Lisp から構成されている。このアプローチは、伝統的な大衆向けソフトウェア("N")とは対照的に、最小限の計画で堅牢なツールを個人ニーズのために構築することは非常に実現可能であることを示している("1")。