🧩 使用泛型探索 Go 语言中的表达式问题(Expression Problem) 🔗

“表达式问题(Expression Problem)”是编程语言设计中的一个经典难题。在本文中,我们将参考《Featherweight Go》论文中提出的思路,探索如何使用泛型在 Go 语言中应对这一问题,并剖析 Go 当前泛型实现的局限性。

📑 目录 🔗


❓ 什么是表达式问题? 🔗

为了理解这一挑战,我们可以先看看《Featherweight Go》论文是如何定义它的:

“目标是按不同分支情况(cases)定义一个数据类型,使人们既可以为该数据类型添加新的分支情况,也可以在该数据类型之上添加新的函数,同时无需重新编译现有代码,并保持静态类型安全。”

论文提供了一个非常形象的类比来帮助直观理解这一点:

“一门语言能否解决表达式问题,是衡量其表达能力的重要指标。我们可以把数据类型的不同分支情况看作表格中的‘行(rows)’,把函数看作表格中的‘列(columns)’。在函数式编程语言中,行是固定的(数据类型声明中的各个分支),但很容易添加新的列(函数);而在面向对象编程语言中,列是固定的(类声明中的方法),但很容易添加新的行(子类)。我们希望让添加行或添加列都变得同样轻松。”

简而言之:一个真正的解决方案允许我们在无需修改现有源代码的前提下,向代码中轻松添加新的数据变体(行)以及新的操作/行为(列)。


🚧 现代 Go 语言中的局限性 🔗

原版《Featherweight Go》草案中提出的解决方案依赖于一项最终未能进入 Go 1.18 泛型正式版的功能。

具体来说,论文中的理想方案假设你可以对方法接收者(method receiver)施加比基础结构体(struct)定义更严格的类型约束。例如,在理想情况下,我们希望能够这样写:

// 假设语法(并非合法的 Go 代码):
// 直接对方法接收者施加额外的类型约束
func (e Plus[T Evaler]) Eval() int
func (e Plus[T Stringer]) String() string

然而,Go 语言的泛型实现要求方法接收者的类型约束必须与结构体的类型定义完全一致。你无法按单个方法逐一添加新的约束要求(如 Evaler 或 Stringer)。


🛠️ 一种折中的变通方案 🔗

由于我们无法在方法级别约束类型参数,我们被迫退而求其次,在结构体级别对它们进行约束。

下面是一个经过调整的代码示例,可以在 Go 1.18+ 泛型环境下完美运行。但请注意,我们被迫预先定义了一个组合接口 Expr。由于每次想要添加新操作时都必须修改这个接口,这并不是表达式问题的真正解决方案,而是根据论文思路改编的一种实用折中方案:

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. 折中妥协:我们必须预先定义一个组合接口。
// 如果后续想要添加新操作,就不得不修改这个接口,
// 这违反了表达式问题的核心原则。
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》论文最初设想的那样原生解决表达式问题。由于要求类型约束必须定义在结构体级别而非方法级别,开发者在为泛型类型添加新操作时,仍然需要预先规划组合接口或修改现有代码。


📚 免费下载我的电子书 🔗

获取《卡乌埃的生活方式》精选离线版——一份全面优化 🍏 健康、💰 财富、❤️ 爱 与 🧘 意义 的实践指南。

卡乌埃的生活方式

卡乌埃的生活方式

📬 订阅邮件通讯 🔗

当我发布关于美好生活的新随笔、读书笔记和实践指南时接收通知。绝无垃圾邮件——可随时退订。


💬 评论与讨论 🔗

有任何问题、见解或建议吗?欢迎在下方分享您的想法。评论功能由 GitHub Discussions 驱动(无广告、无垃圾信息)——点击下方的使用 GitHub 登录即可发表评论或互动(注册免费 GitHub 账号不到一分钟即可完成)。



最后更新于 2026年10月11日.

免责声明:本站所有内容仅供信息参考与教育用途,不构成任何医疗、心理、财务、投资、税务或法律建议。此处呈现的观点、整理与见解仅代表我个人,不代表我雇主的立场。书籍、演讲及文章摘要均为独立的教育性评论,其知识产权归原作者所有。本站可能使用 AI 工具辅助编辑、排版与归纳;尽管我们竭力确保准确无误,但仍可能存在疏漏。隐私政策:本站使用第三方服务提供可选的邮件通讯订阅(follow.it)、评论(giscus / GitHub Discussions)以及嵌入媒体(YouTube),这些服务可能会依据各自的隐私政策处理访客的 IP 地址和 Cookie。