
2026/01/26 8:01
Show HN:すべてが値渡しの小さなプログラミング言語
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Herd は軽量の趣味向けプログラミング言語で、値を明示的に可変(
var)と宣言しない限りすべて不可変として扱います。書き込み時には参照カウントを使って値がコピーされるため、隠れた副作用や参照循環はありません。この設計により GC は参照カウントのみで動作でき、ロックなしで予測可能な並行コードを書くことができます。変更は set 関数を介してのみ発生します。
構文は意図的に最小限です:匿名関数(
\a b …)は可変長引数(..rest)をサポートし、パターンマッチングではリスト/辞書の分解に ! を使用し、switch … on {} 構文を備えています。パイプ演算子 | は呼び出しを連鎖させ、|= は変数を直接変更します。モジュールはテーブル(return {…})を返してエクスポートされ、import 'file' でインポートします。
Herd はシステムコール、IO、コレクション(List, Dict)、数学、ビット演算、乱数生成、文字列操作、ファイル操作の標準ライブラリを備えており、さらに
Parallel.parallelMap と parallelRun を通じた並行実行も可能です。動的型付けでユーザー定義型はなく、言語をシンプルに保っています。
内部では Cranelift に基づく単一パス Just‑In‑Time コンパイラとプリミティブ用の NaN‑boxing を使用しています。ベンチマークでは CPython や Node.js と競合する速度(例:binarytrees は JS より高速、mandelbrot は遅い)が示されており、同時に明確でレースフリーな並行性を提供します。
Text to translate
(incorporating missing elements):**
Herd is a lightweight hobby‑programming language that treats every value as immutable unless you explicitly declare it mutable (
var). Values are copied on write using reference counting, so there are no hidden side‑effects or reference cycles—this lets the GC rely solely on ref‑counting and makes concurrent code predictable without locks. Mutation occurs only via the set function.
The syntax is deliberately minimal: anonymous functions (
\a b …) support variadic arguments (..rest); pattern matching uses ! for destructuring lists/dicts and a switch … on {} construct; the pipe operator | chains calls, with |= mutating variables in place. Modules export by returning a table (return {…}) and are imported with import 'file'.
Herd ships with standard libraries for system calls, IO, collections (List, Dict), math, bitwise ops, randomness, strings, files, and parallel execution via
Parallel.parallelMap and parallelRun. It is dynamically typed with no user‑defined types, keeping the language simple.
Internally it uses a single‑pass Just‑In‑Time compiler built on Cranelift and NaN‑boxing for primitives. Benchmarks show speeds competitive with CPython and Node.js (e.g., binarytrees faster than JS; mandelbrot slower), while still offering clear, race‑free parallelism.
本文
Herd
Herd はすべてが値であるというシンプルな解釈型プログラミング言語です。
免責事項:これは趣味用の言語です。本格的に使うことはおすすめしません。
Herd の特徴
Herd では、リストや辞書も含めてすべてが 値渡し(pass‑by‑value)です。
関数へリストや辞書を渡しても、元のコピーは変更できません。
var foo = { a: 1 }; var bar = foo; // foo のコピーを作る set bar.a = 2; // bar を変更(コピーが作られる) println foo.a bar.a; // 1 2 と出力される
動作原理
文字列・リスト・辞書などの参照型は参照カウントで管理されます。
参照型をコピーするとカウントが増加します。変更時には次のように動きます。
- 参照が一つだけなら、値をその場で変更できる。
- 参照が複数ある場合は「shallow copy(浅いコピー)」を作り、そのコピーに変更を適用する。
すべての値は
var と宣言しない限り不変(immutable)なので 循環参照 は起こり得ません。そのためガベージコレクタは循環検出を行う必要がありません。
他言語との比較
| 言語 | 類似点 / 相違点 |
|---|---|
| Swift | 可変値セマンティクスを採用しているが、型安全でより複雑。 |
| Matlab / R | 配列のコピーオンライトだが、参照カウント減算が不安定で頻繁にコピーが発生する。 |
| PHP | 配列のみ値渡しで、オブジェクトは参照渡し。 |
| Perl | 配列・ハッシュは値渡しだが、多くの方法で保証を破ることができる。 |
言語ツアー
Hello world
println 'Hello, world!'
変数
x = 1; // 不可変変数 var y = 2; // 可変変数 set y = 3; // 可変変数を変更
型
| 型 | 説明 |
|---|---|
| ユニット型(値がない) |
| 真偽値 (, ) |
| 64‑ビット浮動小数点 |
| テキスト文字列(シングルクォート) |
| 順序付きコレクション、例 |
| キーと値のペア、例 |
| 関数値、例 |
リストと辞書
var list = ['a', 'b', 'c']; set list.[1] = 'z'; println list; // ['a', 'z', 'c'] var dict = { x: 1, y: 2 }; set dict.x = 3; println dict; // { x: 3, y: 2 } x = 10; y = 20; dict = { x, y }; // ショートハンドで { x: x, y: y }
ブロック
x = ( a = 2; b = 3; a + b ); println x; // 5
関数
すべての関数は匿名構文で書く。
// 二引数関数 multiply1 = \a b\ ( return a * b; ); x = multiply1 3 4; println x; // 12 // 単一式ボディ(return は不要) multiply2 = \a b\ (a * b); // シンプルな式構文 double = \n\ n * 2; // 引数なし関数 getRandomNumber = \\ Math.randomInt 0 100; println getRandomNumber (); // ランダム整数 // 可変長引数(最後のパラメータは `..`) min = \first ..rest\ ( var minVal = first; for x in rest do ( if x < minVal then set minVal = x ); minVal ); println min 5 3 8; // 3
値と可変性
var と宣言しない限り、すべては不変です。リストや辞書も変更時にコピーされます。
list = ['a', 'b']; // 不可変 set list.[0] = 'A'; // ❌ 変更不可 var list1 = ['a', 'b']; var list2 = list1; // コピー set list2.[0] = 'c'; println list1; // ['a', 'b'] println list2; // ['c', 'b']
関数へ値を渡すときもコピーが作られます。
doubleEveryItem = \var list\ ( for [i, x] in List.enumerate list do set list.[i] = x * 2; list ); list1 = [3, 4]; list2 = doubleEveryItem list1; println list1; // [3, 4] println list2; // [6, 8]
パターンマッチング
! を使ってリストや辞書を分解します。
list = [1, 2]; ![a, b] = list; println a; // 1 println b; // 2 dict = { x: 10, y: 20 }; !{ x, y } = dict; println x; // 10 println y; // 20
switch 式:
x = [1, 2, 3]; y = switch x on { [] => 'Empty list', [1, ...rest] => 'Begins with 1, then ' ++ toString rest, [a] => 'Single item: ' ++ toString a, _ => 'Something else' }; println y; // Begins with 1, then [2, 3]
可変変数への分解:
list = [1, 2]; [a, var b] = list; set b = b + 10; println [list, a, b]; // [[1, 2], 1, 12]
パイプ演算子
関数呼び出しをチェーンします。
x = [1, 2, 3] | List.map \(_ * 2) // 各要素を二倍 | List.filter \(_ > 3); // 3より大きいものだけ残す println x; // [4, 6]
|= でインプレース変更:
var x = [1, 2]; set x |= List.push 3; println x; // [1, 2, 3]
モジュールとインポート
コードを返してエクスポートします。
// file1.herd x = 42; double = \n\ n * 2; return { x, double };
他でインポート:
!{ x, double } = import 'file1.herd'; println double x; // 84 File1 = import 'file1.herd'; println File1.double File1.x; // 84
標準ライブラリ
モジュールは事前にインポートされ、
Module.function でアクセスします。
| モジュール | 用途 |
|---|---|
| System | システム関数(時間、引数) |
| IO | ファイル I/O |
| List | リストユーティリティ |
| Dict | 辞書ユーティリティ |
| Math | 数学と定数 |
| Bitwise | 整整数ビット演算 |
| Random | 乱数生成 |
| String | 文字列ユーティリティ |
| File | ファイルシステム操作 |
| Parallel | マルチスレッド |
グローバル関数として
not, range, assert, toString, print, println, len が用意されています。
マルチスレッド
list = [1, 2, 3]; squared = Parallel.parallelMap list \(_ * _); println squared; // [1, 4, 9] getOrder = \id\ (...); // TODO getCatalog = \\ (...); // TODO orderId = 123; ![order, catalog] = Parallel.parallelRun [ \\ getOrder orderId, \\ getCatalog () ];
データ競合は起こらず、スレッド間の通信は戻り値で行います。
設計方針
動的型付け
簡潔さとインタープリタ設計の実験を重視して採用しました。
ユーザー定義型
現在は存在しません。将来的には構造体や多相ディスパッチを追加する予定です。
セミコロン
読みやすさのために使用していますが、文法上必須ではありません。
変数宣言
デフォルトで不変(
=)。可変にしたい場合は var または set を使います。
関数構文
Rust のクロージャ (
\) と ML スタイルの呼び出しを混ぜたハイブリッドです。カリー化はなく、部分適用は \(func x _) で実現します。
パフォーマンス
- Cranelift を使った単一パス JIT → CPython と競合的、最新の JS ランタイムより遅い。
- プリミティブは NaN‑boxing でヒープ割り当てを回避。
- 主なボトルネック:ホットループでの原子参照カウントオーバーヘッド、単一パス JIT の最適化不足。
ベンチマーク(i5‑13600KF):
| ベンチ | Herd | JavaScript | Python |
|---|---|---|---|
| binarytrees | 1285 ms | 789 ms (-38.6%) | 1992 ms (+55.0%) |
| binarytrees-m | 211 ms | – | – |
| helloworld | 25.9 ms | 53.4 ms (+106.1%) | 26.8 ms (+3.6%) |
| iterables | 630.6 ms | 1360.9 ms (+115.8%) | – |
| mandelbrot | 485.6 ms | 146.1 ms (-69.9%) | 3109.8 ms (+540.3%) |
| nbody | 2998 ms | 86.0 ms (-97.1%) | 1898 ms (-36.7%) |