Semantic Compression (2014)

2025/12/08 1:55

Semantic Compression (2014)

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

要約

Japanese Translation:

概要:
著者は従業員、マネージャー、契約社員のための複雑な継承階層を含む従来の C++ オブジェクト指向パターンを過剰で型安全性に欠けると批判しています。彼は「圧縮志向プログラミング」手法を提案し、まず具体的でケース固有のコードを書き、少なくとも二つ以上の類似インスタンスが現れた時だけリファクタリングする(「コードを早期に再利用可能にしない」)と述べています。マネージャークラスをそのベースにテンプレート化すると多重継承の衝突を解消できます。著者は長年続く不十分な OOP 習慣への苛立ちから、この実用的な姿勢を取るようになりました。
彼は The Witness エディタ UI にこのアプローチを示し、共通レイアウトロジックを

Panel_Layout
構造体とヘルパー関数(
row()
window_title()
push_button()
complete()
)に抽出し、計算をコンストラクタへ移動させ、共有スタックフレームを作成しています。結果として、各ボタン追加がわずか二行で済む簡潔な UI コードになります。
この手法は重複を削減しつつ、コード全体のライフサイクルにおいて人間の労力を低く保ちます。今後の記事では、同じ再利用可能構造を使ってさらに多くの UI 機能を追加するために、この圧縮技術を拡張していきます。

本文

イントロダクション

C++ のプログラミング方法を皆さんが知っていることは間違いありません。
つまり、最初に言語を定義した顔立ちの濃い仲間たちによる素晴らしい書籍を読んだ経験があるということでしょう。その結果、実際の問題を解決する C++ コードを書くためのベストプラクティスを学びました。

まずは「給与計算システム」のような現実世界の問題を見てみます。そこには複数形の名詞がいくつかあります:「employees」「managers」などです。したがって、最初にすべきことはそれぞれの名詞に対してクラスを作ることです。少なくとも「employee」クラスと「manager」クラスが必要になります。

しかし本質的には両者とも人間です。そこで「person」という基底クラスを設け、従業員かマネージャーかに関係なく、人として扱えるようにします。これは非常にヒューマンであり、他のクラスが企業機械の歯車のように感じられないようにする効果があります。

ただし問題が残ります。マネージャーは従業員でもあるはずです。つまり manager は employee から継承すべきであり、employee は person から継承すべきでしょう。ここで本当にやりたいことに近づきます。実際のコードを書いていないものの、オブジェクトをモデル化することで、後は自然とコードが書けるようになります。

ところが……あれ? コントラクター(契約社員)もいるかもしれません。従業員ではありませんから「contractor」クラスが必要です。 contractor は person から継承できるでしょう。これなら非常にシンプルに思えます。

しかしマネージャーはどちらのクラスから継承すべきか?

  • employee から継承すると、契約ベースで働くマネージャーが作れません。
  • contractor から継承すると、フルタイムのマネージャーが作れません。

これは Simplex アルゴリズムに匹敵するほど難しい問題です。

一度は manager を両方から継承して片方だけを使わないという手も考えましたが、それでは型安全性が足りません。JavaScript のようなスクリプト言語ではなく、C++ で安全にやる必要があります。そこで解決策として、manager クラスをテンプレート化します。ベースクラスをテンプレートパラメータとし、manager を扱う全てのコードも同様にテンプレート化するのです。

これが最高の給与計算システムになるでしょう! すべてのクラスとテンプレートを設計したら、エディタを起動して UML 図を書き始めます。


プログラマが投稿するプログラミング記事

先ほど書いた内容が風刺的であったならいいのですが、残念ながら実際に世界中にはこのように考えるプログラマが多く存在します。
私は「Bob the Intern」のことではなく、有名な講師や著者を含むさまざまなプログラマを指しています。また、私自身もかつては同じ思考パターンに陥った経験があります。18 歳でオブジェクト指向プログラミング(OOP)に触れ、24 歳まで「すべてが馬鹿げている」と悟るまでに時間がかかりました。この気づきは RAD Game Tools の仕事を通じて得たものです。

多くのプログラマがこのような悪いフェーズを経て最終的には効率的に良いコードを書く結論へと至ります。しかし、教育資料の現状は「客観的に悪い」カテゴリに大きく偏っています。これは、優れたプログラミングは方法を知れば非常に直感的であるため、数式や高度なアルゴリズムのように「注目すべき」ものではないからだと考えます。その結果、多くの経験豊富なプログラマが自身のコードを書くことについて投稿しません。
そうすべきです。特別でなくても必要なことであり、良いプログラミングを投稿しない限り、人々は「悪いオブジェクト指向コードを書き続ける」状態から抜け出せません。

そこで次の Witness シリーズでは、実際にコードをコンピュータへ投入する純粋な機械的プロセスについて語ります。特別なアルゴリズムや数式は一切扱わず、すべてコードとその構造だけを掘り下げます。


Jon が正しいスタートを切る

The Witness のビルトインエディタには「Movement Panel」という UI コンポーネントがあります。これは「rotate 90°」などの操作を行うボタンが並ぶフローティングウィンドウです。当初は小さく、数個のボタンしかありませんでした。しかし私はエディタを拡張する際に多くの機能を追加したため、パネルの内容は大幅に増えました。これには UI 要素を追加する方法を学ぶ必要がありました。

既存コードを見るとこうなっています:

int num_categories = 4;
int category_height = ypad + 1.2 * body_font->character_height;
float x0 = x; float y0 = y;
float title_height = draw_title(x0, y0, title);
float height = title_height + num_categories * category_height + ypad;
my_height = height;
y0 -= title_height;

{ y0 -= category_height;
  char *string = "Auto Snap";
  bool pressed = draw_big_text_button(x0, y0, my_width, category_height, string);
  if (pressed) do_auto_snap(this); }

{ y0 -= category_height;
  char *string = "Reset Orientation";
  bool pressed = draw_big_text_button(x0, y0, my_width, category_height, string);
  if (pressed) { /* … */ } }

// …

Jon(オリジナルの開発者)は成功への道筋を非常に分かりやすく作ってくれました。多くの場合、シンプルな機能を書いたコードは不要な構造と間接参照で複雑化していますが、ここでは「タイトルバーを配置し、描画し、その下に Auto Snap ボタンを置き、押されたら自動スナップを実行…」という流れがまるで人に UI パネルを作る手順を書いているかのように明確です。これこそプログラミングの正しい姿です。

ただし、このコードは大量の UI を扱うには不向きです。レイアウト作業がすべて行内でハードコーディングされており、複雑な配置(同じ行に 4 個のボタンなど)になるとさらに煩わしくなります。

{ y0 -= category_height;
  float w = my_width / 4.0f;   // 各ボタンの幅
  float x1 = x0 + w;
  float x2 = x1 + w;
  float x3 = x2 + w;

  unsigned long button_color, button_color_bright, text_color;
  get_button_properties(this, motion_mask_x, &button_color,
                        &button_color_bright, &text_color);
  bool x_pressed = draw_big_text_button(x0, y0, w, category_height, "X",
                                        button_color, button_color_bright,
                                        text_color);

  // … Y, Z, Local の繰り返し …
}

そこで私は「コードを簡素化するために基盤となるロジックを抽象化したい」と考えました。何故そう感じたのか? それは「効率的なプログラミング」=「開発者がコーディング・デバッグ・変更・再利用までに必要な人間労力を最小化すること」に対する直感です。


効率性とは何か

コードを書き上げる際の二つの主要タスクは、① 何を処理機が実行すべきか決定し、② それを使用言語で最も効率的に表現することです。後者が多くの場合時間と労力を占めます。

経験豊かなプログラマは「効率」とは単なるパフォーマンスの最適化ではなく、「開発プロセス自体」の最適化であると理解しています。つまり、コードを書き、動かし、修正し、デバッグし、他用途に再利用するまでの総合的な人間労力を削減することです。

この観点から、私が結論付けた最も効率的なプログラミング手法は「コードを辞書圧縮器(コンプレッサー)として扱う」ことです。実際に PKZip のようにコードを圧縮し、意味的に小さくする――重複や類似性を排除して本質のみを残す――というイメージです。

これはボトムアップのアプローチであり、「リファクタリング」と呼ばれることもあります。リファクタリングはコードを整理し、再利用可能にする作業ですが、私が強調したいポイントは「実際に使ってみてから抽象化すべき」という点です。


Compression‑Oriented Programming(圧縮志向プログラミング)の見え方

良いコンプレッサーのように、少なくとも 2 回以上出現したパターンを再利用します。多くの人は「すぐに再利用可能なコードを書く」ことを誤解しがちですが、実際には まず具体的で実行可能なコードを書き、それから重複を見つけて共通化する べきです。

  • 1 回だけ書くと、何が必要か把握できない
  • 2 回目以降に共通点を抽出すれば、再利用性の高い構造が自然に浮上します

また、新しい場所で既存コードを再利用したい場合は「そのまま使う」「修正して使う」「新たなレイヤーを追加する」の三択です。この判断は実際に使ってみての経験から来ます。

最終的に、圧縮されたコードは読みやすく、保守・拡張が容易になります。


Witness UI コードへの Compression

まず、C++ の関数はローカル変数を自己完結させるため、同じ計算を何度も書きたくなります。例えば:

int category_height = ypad + 1.2 * body_font->character_height;
float y0 = y;
// …y0 -= category_height;…
// …y0 -= category_height;…

これらはすべて同じパネル UI の構造です。他のパネルでも同様に開始時・ボタン計算などが繰り返されています。そこで 共通するデータを構造体としてまとめます

struct Panel_Layout {
    float width;        // 「my_width」からリネーム
    float row_height;   // 「category_height」からリネーム
    float at_x;         // 「x0」からリネーム
    float at_y;         // 「y0」からリネーム
};

これを使ってコードを書き直すと:

Panel_Layout layout;
int num_categories = 4;

layout.row_height = ypad + 1.2 * body_font->character_height;
layout.at_x = x;
layout.at_y = y;

float title_height = draw_title(layout.at_x, layout.at_y, title);
float height = title_height + num_categories * layout.row_height + ypad;
my_height = height;

layout.at_y -= title_height;

// …

まだ大きな改善ではありませんが、共通部分を分離した第一歩です。次に 関数化 してさらに簡素化します。

Panel_Layout::Panel_Layout(Panel *panel, float left_x, float top_y,
                           float width) {
    row_height = panel->ypad + 1.2 * panel->body_font->character_height;
    at_y = top_y;   // 初期 y を記憶
    at_x = left_x;
}

void Panel_Layout::row() { at_y -= row_height; }

void Panel_Layout::window_title(char *title) {
    float title_height = draw_title(at_x, at_y, title);
    at_y -= title_height;
}

こうするとメインコードは:

Panel_Layout layout(this, x, y, my_width);
layout.window_title(title);

int num_categories = 4;
float height = title_height + num_categories * layout.row_height + ypad;
my_height = height;

{ layout.row();
  bool pressed = layout.push_button("Auto Snap");
  if (pressed) do_auto_snap(this); }

{ layout.row();
  bool pressed = layout.push_button("Reset Orientation");
  if (pressed) { /* … */ } }

// …
layout.complete(this);

さらに

push_button
をインライン化します。

bool Panel_Layout::push_button(char *text) {
    return panel->draw_big_text_button(at_x, at_y, width,
                                       row_height, text);
}

最終的に得られるコードは以下のようになります:

Panel_Layout layout(this, x, y, my_width);
layout.window_title(title);

layout.row();
if (layout.push_button("Auto Snap")) do_auto_snap(this);

layout.row();
if (layout.push_button("Reset Orientation")) { /* … */ }

layout.complete(this);

これで、重複した計算やボイラープレートが完全に排除され、読みやすさと保守性が大幅に向上しました。


まとめ

  1. まず具体的なコードを書いて動かす
  2. 重複を見つけたら共通化して圧縮する
  3. 抽象化は少なくとも二例以上出現した後に行う
  4. イテレートしながら段階的にコードを簡素化する

この手法は「意味的コンプレッション」によるものです。コードが小さくなると、読みやすさ・保守性・拡張性が飛躍的に向上します。ぜひ実際のプロジェクトで試してみてください。

同じ日のほかのニュース

一覧に戻る →

2025/12/08 2:18

I failed to recreate the 1996 Space Jam website with Claude

## Japanese Translation: ## 要約 著者は、Claude AI を使って 1996 年の Warner Bros の「Space Jam」ランディングページをスクリーンショットとアセットフォルダから再構築しようとしました。元のサイトは 200 KB 未満の単一 HTML ファイルで、絶対位置決め、テーブルレイアウト、およびタイル状の星空 GIF 背景に依存しています。 **プロセスと所見** 1. **初期試行:** Claude は概算レイアウトを生成しましたが、惑星軌道を誤った位置に配置しました。軌道パターンは認識できたものの、それを再現することには失敗しました。 2. **構造化プロンプト:** 著者は Claude に「知覚分析」「空間解釈」「再構築計画」の各セクションで理由を説明させ、正確なピクセル座標を要求しましたが、Claude はそれらを提供できませんでした。 3. **カスタムツール:** 精度向上のために 50 px → 5 px のグリッドオーバーレイ、ラベル付き座標参照点、色差比較、スクリーンショットサイドバイサイドビューア、およびスクリーンショットを 6 区域に分割するスクリプトを構築しました。 4. **結果:** Claude の調整は目標から 5–10 px 内に留まりましたが、正しい軌道半径(約 350–400 px)には決して収束しませんでした。内部レイアウトが生成されると、その後のフィードバックは元のスクリーンショットではなく、この誤ったモデルに基づいて行われました。 5. **トークナイズ仮説:** 著者は Claude が 16×16 パッチで画像をトークナイズしているため、細かい視覚的粒度が欠如し、セマンティック理解はあるもののピクセル精度が低いと考えました。 6. **ズームインテスト:** 200 % に拡大したスクリーンショットを提供して、大きなパッチで解像度が向上するか確認しましたが、Claude は依然として比例スケーリング指示に従いませんでした。 **結論** このタスクは未解決のままです。実験は Claude の空間推論限界をベンチマークとし、ピクセル単位で正確な画像再構築におけるモデルの現在の制約を示しています。

2025/12/08 7:18

How I block all online ads

## Japanese Translation: > **概要:** > 著者は、ウェブブラウザとモバイルアプリの両方で広告を排除するために長期的かつ多層的なアプローチを説明しています。彼は **Firefox + uBlock Origin** と最小限のフィルタリスト(組み込みのuBlockフィルタ、EasyList、AdGuard – Ads)と「広告でない不快要素」のためのカスタム非広告フィルタを使用します。 > DNS フィルタリングには **Pi‑hole(または AdGuard Home)** を Docker 上で $5 の DigitalOcean ドロップレットに稼働させ、WireGuard VPN の DNS サーバとして設定しています。トラフィックは **クラウドベースの VPN**(DigitalOcean、Hetzner、Azure、Google Cloud、または AWS)を経由し、プラットフォームが公的クラウド IP を検知して広告配信を減らします。 > この設定では **Cloudflare のキャプチャや HTTP エラー** が発生する場合があるため、著者は該当サイトで VPN を無効化しています。また、**Consent‑O‑Matic**(クッキーポップアップ)、**Buster**(キャプチャ)、**SponsorBlock**(動画広告)などのブラウザ拡張機能を推奨します。iOS では **Background App Refresh** をオフにするとデータ収集が減少し、Android では **ReVanced がアプリをパッチできますが、セキュリティリスクがあります** と指摘しています。 > 著者はこの統合戦略を 3 年以上使用しており、現在ほとんど広告を見ることはありません。プラットフォーム別の効果は異なります:YouTube は uBlock Origin + VPN(1週間〜1か月)が必要;Instagram は uBlock Origin のみで十分;Twitch は主に VPN に依存し、数日で効果が現れます;TikTok は両方のツールを使用しますが、数時間だけです。**AdMob** を利用するアプリも DNS ブロックの恩恵を受けます。 > 広告配信ネットワークは数日から数週間でパターンを観察し調整する可能性があるため、継続的な監視が必要です。著者は **Firebog** をブロックリストの良い情報源として引用し、正当なサイトを壊さないように許可リスト(allowlist)を維持する重要性を強調しています。

2025/12/07 23:37

Dollar-stores overcharge cash-strapped customers while promising low prices

## Japanese Translation: ドルジェネラルとファミリードラーは、棚に貼られたタグの価格よりも高い価格で顧客を頻繁に請求し、低所得層の買い物客に不釣り合いな過剰課金が広く発生しています。州検査と独立調査では、一部店舗でエラー率が88%に達するケースや、両チェーン全体で価格設定失敗が一貫して報告されています。 主な例としては、ノースカロライナ州ウィンザーのファミリードラーで23%のスキャンアイテムが過剰請求(同店の4回連続失敗)、オハイオ州ハミルトンのドルジェネラルで76%のエラー率(2022年10月)、ニュージャージー州バウンドブルックのファミリードラーで68%の不一致(2023年2月)があります。2022年1月以降、ドルジェネラルは4,300件以上、ファミリードラーは2,100件以上の価格失敗事例を記録しています。 アリゾナ州(60万ドル)、コロラド州(40万ドル)、ニュージャージー州・バーモント州・ウィスコンシン州・オハイオ州(最大100万ドル)など複数の州がチェーンと訴訟を和解し、連邦および州の司法長官は追加訴訟を提起しています。株主訴訟では、経営陣がシステム的問題を認識していたと主張されています。ニュージャージー州の連邦裁判所は、モバイルアプリ利用に関連する仲裁条項を理由にドルジェネラルに対する集団訴訟を停止し、消費者の救済手段を制限しました。 規制当局は現在の1検査あたり5,000ドル上限を超えるより厳格な執行や高い罰則を課すことができ、さらに州が調査を進めるにつれて追加の和解が生じる可能性があります。影響としては顧客信頼の低下、チェーンへの潜在的財務損失、評判へのダメージ、およびドルストア業界全体での価格設定と人員管理の強化への動きが挙げられます。