利用泛型实现的 golang 缓存装饰器

149 天前
 a132811

项目地址: https://github.com/ahuigo/gofnext#decorator-cases

类似于 python 的 @functools.cache

cache decorator 菲波那切数列示例:

package main
import "fmt"
import "github.com/ahuigo/gofnext"
func main() {
    var fib func(int) int
    var fibCached func(int) int
    fib = func(x int) int {
        fmt.Printf("call arg:%d\n", x)
        if x <= 1 {
            return x
        } else {
            return fibCached(x-1) + fibCached(x-2)
        }
    }

    fibCached = gofnext.CacheFn1(fib)    

    fmt.Println(fibCached(5))
    fmt.Println(fibCached(6))
}
1315 次点击
所在节点    Go 编程语言
6 条回复
keakon
149 天前
看上去不够优雅,但是如果要适配任意数目的参数和返回值,只能用反射来做。
a132811
149 天前
@keakon 多参数的话不需要用反射。
其实以前我用反射实现过,使用起来更复杂、且会损失类型检查,目前用泛型实现就简单许多了。

如果是多参数的话,需要用包装函数(不需要反射)将参数将降维(示例: https://github.com/ahuigo/gofnext#cache-function-with-more-params2

ps: 之所以没有封装成支持任意不定参或 any 类型的函数,是因为目前 go 的泛型参数不支持对约束再做断言。

如果你有更优雅方式的话,望回复告之,也欢迎 issue 、pr 。
keakon
149 天前
@a132811 反射不会损失类型检查,可以动态获取参数类型并构造一个 struct ,但是做不到静态检查。实现自然是会复杂很多,但是对调用者而言是更简单的。不过既然你是做缓存,肯定是在意性能的,我就不推荐用反射了。
a132811
149 天前
@keakon 如果输入用反射而不用泛型,那类型检查就没有了; 输出同理。

或许我没有 get 到你的意思?你是否可以给个伪代码,或者 demo ?

ps: 反射损失的性能很多场景可以忽略不计
keakon
148 天前
@a132811 我的意思是反射可以获取到原函数的每个参数的类型,你可以保存下来,调用时检查参数是不是对应的类型。

但是 Go 不是很动态的语言,反射和泛型也没法结合使用,导致泛型实现的接口没法返回正确的类型(只能是 interface{}),因此没法实现 demo 的 fibCached(x-1) + fibCached(x-2)。不过 demo 里对 fib() 的实现也是有侵入的。

比较类似的例子你可以参考这篇,最后为了优化实现得有点复杂,看看原理就好:
https://keakon.uk/2023/03/24/%E6%8A%8A%E4%BB%BB%E5%8A%A1%E9%98%9F%E5%88%97delayed%E7%A7%BB%E6%A4%8D%E5%88%B0Go%E4%BA%86
a132811
148 天前
@keakon 嗯。
我主要是想确认否有简单优雅的实现(对调用方要简单/直接/安全)、以及更通用的 cache 库。

库本身的复杂性对用户是透明的,内部是否反射+泛型都 ok ,实际上本库的内部就是反射+泛型结合的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/999434

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX