V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
a132811
V2EX  ›  Go 编程语言

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

  •  
  •   a132811 ·
    ahuigo · 2023-12-11 16:50:04 +08:00 · 1621 次点击
    这是一个创建于 380 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目地址: 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))
    }
    
    第 1 条附言  ·  2023-12-13 19:18:06 +08:00

    简写

        var fib func(int) int
        fib = func(x int) int {
            fmt.Printf("call arg:%d\n", x)
            if x <= 1 {
                return x
            } else {
                return fib(x-1) + fib(x-2)
            }
        }
    
        fib = gofnext.CacheFn1(fib, nil)    
    
        fmt.Println(fib(5))
    
    6 条回复    2023-12-13 12:55:46 +08:00
    keakon
        1
    keakon  
       2023-12-11 18:15:28 +08:00
    看上去不够优雅,但是如果要适配任意数目的参数和返回值,只能用反射来做。
    a132811
        2
    a132811  
    OP
       2023-12-11 22:30:21 +08:00
    @keakon 多参数的话不需要用反射。
    其实以前我用反射实现过,使用起来更复杂、且会损失类型检查,目前用泛型实现就简单许多了。

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

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

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

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

    ps: 反射损失的性能很多场景可以忽略不计
    keakon
        5
    keakon  
       2023-12-12 21:30:18 +08:00
    @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
        6
    a132811  
    OP
       2023-12-13 12:55:46 +08:00
    @keakon 嗯。
    我主要是想确认否有简单优雅的实现(对调用方要简单/直接/安全)、以及更通用的 cache 库。

    库本身的复杂性对用户是透明的,内部是否反射+泛型都 ok ,实际上本库的内部就是反射+泛型结合的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3103 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 13:23 · PVG 21:23 · LAX 05:23 · JFK 08:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.