【免费下载链接】fo
An experimental language which adds functional programming features to Go.
Fo(Functional Go)是一门为 Go 添加函数式编程特性的实验性语言。本教程带你快速上手Fo 泛型函数:从声明泛型函数、书写TypeArgs类型实参,到MapSlice实战调用,一篇讲透写法与背后的编译原理。
Fo 是什么?一分钟了解项目
Fo 是 Go 的超集——任何合法的 Go 程序(把扩展名从.go改成.fo)都是合法的 Fo 程序。在此基础上,Fo 额外支持三大泛型能力:
- 泛型命名类型(Generic Named Types),如
Box[T] - 泛型函数(Generic Functions),本文主角
- 泛型方法(Generic Methods),可声明在泛型接收者上
项目说明详见 README.md。
⚠️ 小提示:Fo 目前处于实验阶段且已停止活跃维护,它最大的限制是一次只能编译单个文件,更适合用来学习 Go 编译器与 AST 改造的思路。
快速安装与运行 Fo 文件
Fo 编译器本身用 Go 编写,安装和运行其他 Go 程序一样简单:
go get -u github.com/albrow/fo命令行只有一条命令run,接收一个包含main函数的.fo文件:
fo run main.fo运行流程在入口 main.go 中一目了然:解析 → 类型检查 → 泛型转换 → 调用go run执行生成的 Go 代码。
泛型函数怎么写:TypeParams 声明步骤
泛型函数的类型参数(TypeParams)紧跟在函数名之后,用方括号括起,多个参数用逗号分隔:
FunctionDecl = "func" FunctionName [ TypeParams ] Signature [ FunctionBody ] . TypeParams = "[" identifier { "," identifier } "]"以MapSlice为例:它接收一个函数f和一个切片list,对每个元素应用f后返回新切片:
func MapSliceT T, list []T) []T { result := make([]T, len(list)) for i, val := range list { result[i] = f(val) } return result }记忆口诀:类型参数放函数名后,方括号里逗号分隔,函数签名和函数体里都能用。
TypeArgs 类型实参怎么写?三步到位
这是新手最常卡住的地方。核心规则只有一条:Fo 不支持类型实参推断,调用泛型函数时必须显式给出类型实参(TypeArgs)。
语法规则:
CallExpr = FunctionName ( TypeArgs ) Arguments . TypeArgs = "[" Type { "," Type } "]" .写TypeArgs的三步法:
- 数参数:声明里有几个类型参数,调用时就要填几个类型
- 对位置:按声明顺序一一对应,不能跳、不能换
- 放括号:类型实参写在左括号之前、函数名之后
func incr(n int) int { return n+1 } // 类型实参 [int] 紧跟函数名,再接普通实参 MapSliceint| 写法 | 是否正确 | 说明 |
|---|---|---|
MapSliceint | ✅ | 类型实参与参数个数匹配 |
MapSlice(incr, []int{1, 2, 3}) | ❌ | 缺少 TypeArgs,Fo 不做类型推断 |
MapSliceint, uint | ❌ | 声明只有 1 个类型参数T,多写了 |
💡 对比 Go 1.18+ 的泛型:Go 允许编译器推断类型实参,而 Fo 要求全部显式写出,写的时候多花几秒,出错时却一目了然。
MapSlice 实战:同一函数的三种类型实参组合
仓库自带示例 examples/map_slice/main.fo 演示了mapSlice[T, U](两个类型参数版本)的多种调用方式,这是理解多类型参数TypeArgs的最好素材:
func mapSliceT, U U, list []T) []U { ... }- 元素不变:
mapSliceint, int→ 每个元素加 1,输出[2, 3, 4] - 元素转换:
mapSliceint, uint→ 得到[]uint - 字符串处理:
mapSlicestring, string→ 输出[APPLE BANANA CARROT]
可以看到,类型实参的顺序就是T, U的顺序:第一个对应输入类型,第二个对应输出类型。
更多玩法可参考其他示例:
- examples/box/main.fo:泛型类型
Box[T]+ 泛型方法Map[U]的组合使用 - examples/pipe/main.fo:函数式管道示例
- examples/set/main.fo:泛型集合实现
进阶揭秘:TypeArgs 编译后变成什么?
Fo 的底层采用单态化(monomorphization)策略:编译器会为每种类型实参组合生成一个具体的 Go 函数。
对比原始源码 examples/map_slice/main.fo 和生成产物 examples/map_slice/main.go,你能看到:
mapSlice[int, int]→mapSlice__int__intmapSlice[int, uint]→mapSlice__int__uintmapSlice[string, string]→mapSlice__string__string
类型实参之间用双下划线__连接并拼在函数名后;若实参是复杂类型(含[]、*、.等符号),会被 transform/safe_string.go 中的safeSymbolMap替换成下划线,保证生成合法的 Go 标识符。
这一过程的核心逻辑在 transform/transform.go 的concreteTypeName函数中:它读取声明的类型参数与类型实参的映射,拼出名称__实参1__实参2形式的具体类型名。
🔍 想深入源码?类型检查扩展在 types/,AST 节点定义在 ast/ast.go。
常见问题速查(FAQ)
Q1:能省略 TypeArgs 让编译器推断吗?不能。Fo 目前不支持类型实参推断,必须显式书写(详见 README.md 的 Usage 章节)。
Q2:类型实参可以写在函数参数列表里(Go 风格)吗?不能。Fo 的写法是Func[T]后跟普通调用FuncT,类型实参独立于普通参数,两者之间只有括号分隔。
Q3:泛型方法调用时也要写接收者的类型实参吗?不用。如 examples/box/main.fo 所示,x.Val()直接调用即可,接收者的类型信息会透传给方法;只有方法自己声明的额外类型参数(如Map[U]的U)才需要写出。
Q4:Fo 适合用于生产项目吗?项目已停止活跃维护,且限制为单文件编译,建议把它当作学习 Go 泛型、编译器改造与 AST 操作的实验沙盒。
总结
- 泛型函数声明:
func NameT, U,类型参数紧跟函数名 - TypeArgs 调用:
NameT, U,个数与顺序必须和声明严格对应,且必须显式书写 - 编译原理:Fo 通过单态化为每组类型实参生成独立 Go 函数,命名规则为
名称__类型实参
掌握MapSlice这一个例子,你就掌握了 Fo 泛型函数TypeArgs的全部写法。
【免费下载链接】fo
An experimental language which adds functional programming features to Go.
相关推荐
Go泛型实战:理解多类型泛型参数的使用
Go泛型实战:理解多类型泛型参数的使用 本文是《Go泛型从入门到精通》系列教程的一部分,将深入探讨Go语言中多类型泛型参数的使用方法。我们将通过一个实际案例,展
TypeHero 泛型函数参数实战:从 `identity` 到 `mapArray` 掌握泛型函数的核心写法
TypeHero 泛型函数参数实战:从 identity 到 mapArray 掌握泛型函数的核心写法 泛型(Generic)是 TypeScript 类型系统
教育前端后端BenchmarkDotNet 泛型基准测试实战:使用 [GenericTypeArguments] 为开放泛型 Benchmark 类注入类型参数
BenchmarkDotNet 泛型基准测试实战:使用 GenericTypeArguments 为开放泛型 Benchmark 类注入类型参数 泛型 Benc
性能测试开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考