「Expression Problem(表現問題)」は、プログラミング言語設計における古典的な難問の一つです。この記事では、Featherweight Go の論文で提示されたアイデアを参照しながら、Goのジェネリクスを使ってこの問題にどうアプローチできるか、そして現在のGoの実装にはどのような制限があるのかを探っていきます。
この課題を理解するには、Featherweight Go の論文における定義を見るのが分かりやすいでしょう。
「目標は、既存のコードを再コンパイルすることなく、また静的な型安全性を維持したまま、データ型に新しいケース(種類)を追加したり、そのデータ型に対する新しい関数(操作)を追加したりできるように、ケース分けによってデータ型を定義することである。」
論文では、これを視覚的にイメージするための優れた比喩も紹介されています。
「ある言語がExpression Problemを解決できるかどうかは、その言語の表現力を示す顕著な指標となる。ケースをテーブルの『行』、関数を『列』と考えてみよう。関数型言語では、行(データ型宣言内のケース)は固定されているが、新しい列(関数)を追加するのは簡単だ。一方、オブジェクト指向言語では、列(クラス宣言内のメソッド)は固定されているが、新しい行(サブクラス)を追加するのは簡単だ。私たちが望むのは、行と列のどちらも簡単に追加できるようにすることである。」
要するに、真の解決策とは、既存のソースコードを一切変更することなく、新しいデータのバリエーション(行)および新しい操作・振る舞い(列)の両方を、既存のコードにスムーズに追加できるようにすることです。
オリジナルの Featherweight Go のドラフトで提案されていた解決策は、最終的なGo 1.18のジェネリクスリリースには採用されなかった機能に依存しています。
具体的には、論文の理想的な解決策では、ベースとなる構造体(struct)で定義された型制約よりもさらに厳しい型制約をメソッドのレシーバに適用できることを前提としています。たとえば理想的には、次のように書きたいところです。
// 仮の構文(有効なGoコードではありません):
// メソッドのレシーバに直接、追加の型制約を課す
func (e Plus[T Evaler]) Eval() int
func (e Plus[T Stringer]) String() string
しかし、実際のGoのジェネリクス実装では、メソッドレシーバの型制約は構造体の型定義と完全に一致している必要があります。メソッド単位で個別に新しい制約要件(Evaler や Stringer など)を追加することはできません。
メソッドレベルで型パラメータを制約できないため、構造体レベルで制約せざるを得ません。
以下は、Go 1.18以降のジェネリクスで問題なく動作するように調整したコード例です。ただし、事前に統合された Expr インターフェースを定義しなければならない点に注目してください。新しい操作を追加するたびにこのインターフェースを変更する必要があるため、これはExpression Problemの真の解決策ではなく、論文から応用した実用的なワークアラウンド(回避策)となります。
package main
import (
"fmt"
)
// 1. ベースとなるデータ型を定義する
type Num struct {
value int
}
// 2. 操作(列)を定義する
type Evaler interface {
Eval() int
}
type Stringer interface {
String() string
}
// ベース型に対して操作を実装する
func (e Num) Eval() int {
return e.value
}
func (e Num) String() string {
return fmt.Sprintf("%d", e.value)
}
// 3. 妥協点:あらかじめ統合されたインターフェースを定義する必要がある。
// 後から新しい操作を追加したい場合、このインターフェースを編集せざるを得ず、
// Expression Problemの根本ルールに違反してしまう。
type Expr interface {
Evaler
Stringer
}
// 4. ジェネリックなデータ型(行)を定義する
// Goではメソッドレベルの型制約が許可されないため、ここで T を Expr に制約する必要がある。
type Plus[T Expr] struct {
left T
right T
}
func (e Plus[T]) Eval() int {
return e.left.Eval() + e.right.Eval()
}
func (e Plus[T]) String() string {
return fmt.Sprintf("(%s+%s)", e.left.String(), e.right.String())
}
// 5. 実行
func main() {
var e Expr = Plus[Expr]{left: Num{1}, right: Num{2}}
fmt.Println(e.Eval()) // Output: 3
fmt.Println(e.String()) // Output: (1+2)
}
Goのジェネリクスは非常に強力であり、言語に待望の柔軟性をもたらしてくれましたが、Featherweight Go の論文が当初描いていたような形では、Expression Problemをネイティブに解決することはできません。型制約をメソッドレベルではなく構造体レベルで定義する必要があるため、ジェネリック型に新しい操作を追加する際には、依然としてインターフェースを事前に設計しておくか、既存のコードを修正する必要があります。
カウエの生き方のオフライン版を入手しましょう——🍏 健康、💰 富、❤️ 愛、🧘 意味を最適化するための実践ガイドです。
新しいエッセイ、本の要約、より良く生きるための実践ガイドを公開した際にお知らせします。スパムはありません——いつでも配信停止できます。
質問、ご意見、提案はありますか?ぜひ下であなたの考えを共有してください。コメント欄は GitHub Discussions(広告なし・スパムなし)を利用しています——下の GitHub でサインイン をクリックしてコメントやリアクションを残してください(無料の GitHub アカウントは1分未満で作成できます)。
最終更新日: 2026/10/11.
免責事項:当サイトのコンテンツは情報提供および教育のみを目的としており、医療、心理、金融、投資、税務、または法律上の助言を構成するものではありません。ここに掲載されている考え、キュレーション、意見はすべて私個人のものであり、雇用主の見解を代表するものではありません。書籍、講演、記事の要約は独立した教育的な解説であり、それぞれの著者の知的財産です。編集、フォーマット、要約の補助としてAIツールを使用する場合がありますが、正確性を期すよう努めているものの誤りが含まれる可能性があります。プライバシー:当サイトでは、任意のニュースレター購読(follow.it)、コメント機能(giscus / GitHub Discussions)、および埋め込みメディア(YouTube)に外部サービスを利用しており、これらのサービスはそれぞれのプライバシーポリシーに従って訪問者のIPアドレスやCookieを処理する場合があります。