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

这段 go 代码始终理解不到

  •  
  •   Grocker · 262 天前 · 7025 次点击
    这是一个创建于 262 天前的主题,其中的信息可能已经有所发展或是发生改变。
    package main
    
    import "fmt"
    
    type Greeting func(name string) string
    
    func (g Greeting) say(n string) {
    	fmt.Println(g(n))
    }
    
    func english(name string) string {
    	return "Hello, " + name
    }
    
    func main() {
    	greet := Greeting(english)
    	greet.say("World")
    }
    

    这段代码为什么会输出 Hello, World,始终理解不到

    69 条回复    2024-04-04 22:14:23 +08:00
    LsLsLsLsLs
        1
    LsLsLsLsLs  
       262 天前   ❤️ 1
    定义 Greeting 类型:

    go
    Copy code
    type Greeting func(name string) string
    这里定义了一个新的类型 Greeting ,它是一个函数签名。这意味着任何具有相同签名(即接受一个 string 类型的参数并返回一个 string 类型)的函数都可以被看作是一个 Greeting 类型。

    为 Greeting 类型添加 say 方法:

    go
    Copy code
    func (g Greeting) say(n string) {
    fmt.Println(g(n))
    }
    这个方法接受一个 Greeting 类型的接收器和一个 string 类型的参数。在这个方法内部,它调用了 Greeting 类型的函数(这里的 g ),传入了 n 作为参数,并打印了该函数的返回值。因为 Greeting 是一个函数类型,所以这里 g(n) 实际上是在调用这个函数。

    定义 english 函数:

    go
    Copy code
    func english(name string) string {
    return "Hello, " + name
    }
    这是一个简单的函数,接受一个 string 类型的参数,并返回一个新的 string ,其中包含了问候语。这个函数符合 Greeting 类型的定义。

    在 main 函数中使用:

    go
    Copy code
    func main() {
    greet := Greeting(english)
    greet.say("World")
    }
    这里首先将 english 函数转换成 Greeting 类型,并赋值给 greet 变量。这是可能的,因为 english 符合 Greeting 类型的定义。
    然后,调用了 greet 的 say 方法,并传入了 "World" 作为参数。这将会打印 "Hello, World",因为 english 函数被调用,并以 "World" 作为参数。
    这个程序的核心思想是通过函数类型和方法,实现了对函数的封装和扩展。在这个例子中,Greeting 函数类型通过添加 say 方法,能够以一种更结构化的方式使用函数。这种模式在 Go 中是一种强大的设计方式,允许开发者以灵活且富有表达力的方式编写代码。
    Grocker
        2
    Grocker  
    OP
       262 天前
    @2677672 chatgpt?
    deplives
        3
    deplives  
       262 天前
    很难理解么?
    say 里面是啥?不就一个 g(n) 么
    g 是啥,g 不就是 func english
    n 是啥 n 不就是 "World"

    那 g(n) 不就是 english("World")
    fmt.Println(g(n)) 不就应该是 Hello, World
    seers
        4
    seers  
       262 天前 via Android   ❤️ 5
    pass func like pass value
    hemingway
        5
    hemingway  
       262 天前   ❤️ 2
    type Greeting func(name string) string 因为这个 typedef ,所以
    greet := Greeting(english)
    这一行的效果是,greet := english
    那么 say 里面:
    fmt.Println(g(n)) 等价于 fmt.Println(english(n))
    Kumo31
        6
    Kumo31  
       262 天前   ❤️ 1
    做等价代换就清晰了,首先可以看成
    func say(g Greeting, name string) {
    fmt.Println(g(name))
    }

    func main() {
    say(Greeting(english), "World")
    }

    然后
    func say(g func(name string) string, name string) {
    fmt.Println(g(name))
    }

    func main() {
    say(english, "World")
    }

    接着
    func say(name string) {
    fmt.Println(english(name))
    }

    func main() {
    say("World")
    }

    最后

    func say(name string) {
    fmt.Println("Hello, " + name)
    }

    func main() {
    say("World")
    }
    xuing
        7
    xuing  
       262 天前   ❤️ 2
    Greeting 是接口,english 是实现,通过 Greeting(english)进行了强转。调用 say 方法,say 方法里面的 g(n)就是调用 english 方法本身。

    GPT4 say:

    这段代码定义了一个函数类型 `Greeting`,一个接受字符串并返回字符串的函数。`english` 函数符合 `Greeting` 类型的定义,因此可以将 `english` 函数转换为 `Greeting` 类型,并赋值给变量 `greet`。

    `Greeting` 类型还定义了一个方法 `say`,该方法接受一个字符串参数 `n`,并使用 `Greeting` 类型的函数(本例中是 `english` 函数)处理这个参数,然后输出结果。

    这里是具体的步骤:

    1. 定义 `Greeting` 类型为一个函数类型,它接受一个 `string` 类型的参数,并返回一个 `string` 类型的结果。
    2. 定义 `english` 函数,该函数符合 `Greeting` 类型:接受一个 `string` 类型的参数,返回一个拼接了 "Hello, " 和参数的 `string` 类型的结果。
    3. 在 `main` 函数中,将 `english` 函数转换为 `Greeting` 类型,并赋值给 `greet` 变量。
    4. 调用 `greet` 变量的 `say` 方法,并传递 "World" 作为参数。
    5. `say` 方法内部调用 `greet`(即 `english` 函数),传递 "World" 作为参数,

    得到返回值 "Hello, World"。
    6. 使用 `fmt.Println` 输出这个返回值。

    所以,当运行这段代码时,它会输出 "Hello, World"。这是因为 `greet.say("World")` 实际上是调用 `english` 函数,将 "World" 作为参数,然后输出结果。
    LsLsLsLsLs
        8
    LsLsLsLsLs  
       262 天前
    @Grocker
    prenwang
        9
    prenwang  
       262 天前
    在 Go 中,函数是一等公民, 函数式编程, 非常巧妙。

    Greeting(english) 是函数转换, 函数签名必须一致, 也就是参数,返回值都必须一致。

    适用的场景太多了, 比如回调函数,web 框架的中间件等

    同时也体现了 go 的类型模式的优点, 把静态语言玩出这种魔法确实强。
    tangqiu0205
        10
    tangqiu0205  
       262 天前   ❤️ 1
    首先定义了 Greeting 为 func 类型, 并且他有一个 say 方法.
    greet := Greeting(english) 这段是把 english 转换成 Greeting 类型,
    所以 greet 可以调用 say()方法, 又由于 say 方法有接收者类型 g,
    g 是一个 func 类型,所以这块可以调用 g 的方法.
    zrlhk
        11
    zrlhk  
       262 天前
    这段 Go 语言程序定义了一个简单的示例,用于展示函数类型和方法的使用。让我逐步解释这段代码:

    首先,通过 package main 声明该程序是一个可独立运行的程序,并且在编译后会生成可执行文件。

    import "fmt"引入了 Go 语言标准库中的 fmt 包,用于格式化输入输出。

    接着定义了一个类型为 func(name string) string 的别名 Greeting ,表示这是一个接收 string 类型参数并返回 string 类型结果的函数类型。

    然后定义了一个方法 say ,该方法属于类型 Greeting ,接收一个 string 类型参数 n ,通过调用 g(n)来输出对应的问候语。

    接下来定义了一个普通函数 english ,实现了一个简单的英文问候功能,接收一个 name 参数,返回"Hello, " + name 的字符串。

    在 main 函数中,创建了一个变量 greet ,将类型为 Greeting 的 english 函数赋值给它,相当于将 english 函数转换为 Greeting 类型的变量 greet 。

    最后通过 greet.say("World")调用了 say 方法,传入参数"World",实际上是调用了 english 函数并输出了"Hello, World"这个问候语。

    总结:这段代码演示了如何定义函数类型、方法以及函数的转换与调用。通过这个示例,展示了 Go 语言中函数类型和方法的灵活性和方便性。
    totoro52
        12
    totoro52  
       262 天前
    相当于定义了一个接口, 下面就是实现的方法,这么理解好理解点。
    dhb233
        13
    dhb233  
       262 天前
    我已经尽力去写个好理解的变量名字了,有点长
    ·
    package main

    import "fmt"

    type strFunc func(name string) string

    func (g strFunc) callFuncAndPrintReturnVal(n string) {
    fmt.Println(g(n))
    }

    func addHelloPrefix(name string) string {
    return "Hello, " + name
    }

    func main() {
    addHelloPrefixFunc := strFunc(addHelloPrefix)
    addHelloPrefixFunc.callFuncAndPrintReturnVal("World")
    }
    ·
    yyf1234
        14
    yyf1234  
       262 天前 via iPhone
    很典型的 callback 用法,适用于 func 作为参数的场景
    tinyfry
        15
    tinyfry  
       262 天前
    Greeting 和 english 的签名相同
    deorth
        16
    deorth  
       262 天前 via Android
    你以前是写啥的,没有变量存函数的概念么
    6IbA2bj5ip3tK49j
        17
    6IbA2bj5ip3tK49j  
       262 天前 via iPhone
    好奇,是初学者吗……
    root71370
        18
    root71370  
       262 天前
    像高数的换元法。。
    lcy
        19
    lcy  
       262 天前
    类似 C 的类型转换 greet = (Greeting) english
    NessajCN
        20
    NessajCN  
       262 天前
    这个你如果会一点 js 就特别清晰了,就是把函数当成个变量
    譬如这里的 english ,虽然它是个函数,但你可以把他当成跟 i := 1 这样的 i 一样的东西
    你可以理解称 english := func(n string) string { return "Hello, " + n }
    那既然是个变量,你就可以把它放进类型里 , 就像这里的 Greeting
    然后下面定义的 Greeting 的方法 say, 根据它的实现,g 其实就是 english, 那 g(n) 就是 english(n)
    那么 greet.say("World") 就等于 fmt.Println(english("World"))
    0Z03ry75kWg9m0XS
        21
    0Z03ry75kWg9m0XS  
       262 天前   ❤️ 1
    防御性编程 + 1
    Grocker
        22
    Grocker  
    OP
       262 天前
    @xgfan 对 go 了解不深
    Jooeeee
        23
    Jooeeee  
       262 天前
    @root71370 因吹斯汀,高数跟编程语言建立了连接
    wweerrgtc
        24
    wweerrgtc  
       262 天前   ❤️ 2
    结合另一个例子 应该可以看懂
    mellowmemories
        25
    mellowmemories  
       262 天前
    没学过 GO ,浅浅懵一下。

    首先创建了一个 lambda 表达式 Greeting ,定义了一种行为,该行为接收一个 string,并返回一个 string 。

    在 main 函数的第一行,将复合上文中的定义的 english 函数包装成指定的 Greeting lambda 。

    然后 say 函数,似乎是被定义在 Greeting 名下的,作为其方法。
    uiosun
        26
    uiosun  
       262 天前
    给到一个回调函数作为类型,进行后续的类型下的方法处理

    JS 、PHP 蛮多这种的,没啥特别。

    看这个帖才知道,Go 连 func 都能当类型,有点野哈哈。
    gowk
        27
    gowk  
       262 天前
    想知道 Rust 里面是不是也有类似的写法?
    journalistFromHK
        28
    journalistFromHK  
       262 天前
    <p>正在学 go,还没见过 type 为 func 的,以前都是 struct...</p>
    <p>按照我这种新手的理解(应该和楼主一样吧),</p>
    <p>核心就是理解 fmt.Println(g(n)),</p>
    <p>其实在 greet := Greeting(english)之后 greet 就等于 english 了,</p>
    <p>所以 g(n)就等于 english(n),n 又是由 say 的"world"传入...</p>
    <p>最后就相当于执行了 english,参数是 say("world")的参数</p>
    journalistFromHK
        29
    journalistFromHK  
       262 天前
    @journalistFromHK 卧槽 还想着带个格式来着
    CHchenkeyi
        30
    CHchenkeyi  
       262 天前
    他们都说的复杂了,其实很好理解,因为 english 的函数签名和 Greeting 一致,所以可以转换,这边是转换就像 int64 ->int , 是不是就是 int(int64) 这种写法, 那么 greet 就是 english ,然后调用了 say ('world')=>greet(n) 方法, 实际上就是 english('world)
    F7TsdQL45E0jmoiG
        31
    F7TsdQL45E0jmoiG  
       262 天前
    函数式编程还是 LISP 那种看着舒服
    strconv
        32
    strconv  
       262 天前
    > Go 语言中的方法( Method )是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者( Receiver )
    - say 称作方法,(g Greeting)称之为接收者。
    - 方法也是一种函数,接收者也是一个参数

    ```go
    type Greeting func(string) string

    // say 方法接收者
    func (g Greeting) say(name string) {
    fmt.Println(g(name))
    }

    // say2
    // 方法接收者等效于把一个参数前置了,翻译过来就是下面这个函数
    // 这个函数就好理解了
    // 入参:处理字符串的函数、字符串
    // 功能:调用 函数 去处理 字符串
    func say2(g Greeting, name string) {
    fmt.Println(g(name))
    }

    ```
    cmsyh29
        33
    cmsyh29  
       262 天前
    把方法强转为了函数对象。写惯 pojo 容易以为是构造函数了
    cmsyh29
        34
    cmsyh29  
       262 天前   ❤️ 1
    go 只是语法简单,上手程度我觉得不如 java
    zhoujx
        35
    zhoujx  
       262 天前
    就是有点绕而已
    gkinxin
        36
    gkinxin  
       261 天前
    @Livid #1 2677672 #11 zrlhk gpt 回答
    iceiceice9527
        37
    iceiceice9527  
       261 天前
    重点:函数可以是变量。。
    JoeJasper
        38
    JoeJasper  
       261 天前
    go 语言里的 duck typing (鸭子类型)
    Megrax
        39
    Megrax  
       261 天前
    @xuing 谢谢,看到“强转”这两个字就懂了
    FYFX
        40
    FYFX  
       261 天前
    我不懂 go,不过 我好奇回复里面说强转的,这个转换真的发生了吗(由编译器做的)?

    > A function type denotes the set of all functions with the same parameter and result types. The value of an uninitialized variable of function type is nil. https://go.dev/ref/spec#Function_types

    我查了一下 golang 的函数类型是不不包括函数名的,所以 Greeting 和 english 就是同一个函数类型。至于 golang 函数签名和函数类型是不是一回事,也就是说是否包含函数名称这点我看两种说法都有,spec 里面我没看到明确说法。
    leonshaw
        41
    leonshaw  
       261 天前   ❤️ 1
    @FYFX
    Greeting 是一个新的类型,它的 underlying 类型是 func(name string) string
    english 是一个签名是 func(name string) string 的函数
    aw2350
        42
    aw2350  
       261 天前
    多态,委托 ,先好好补习一下面向对象
    yiqiao
        43
    yiqiao  
       261 天前
    OP 有用过 PHP 吗?类似:call_user_func_array
    flyv2x
        44
    flyv2x  
       261 天前
    简单说,其实你的例子,如果去掉 type 定义,直接等价于下面代码:
    ```
    package main

    import "fmt"

    func say(g func(name string) string, n string) {
    fmt.Println(g(n))
    }

    func english(name string) string {
    return "Hello, " + name
    }

    func main() {
    say(english, "World")
    }
    ```
    CHchenkeyi
        45
    CHchenkeyi  
       261 天前
    他们都说的复杂了,其实很好理解,因为 english 的函数签名和 Greeting 一致,所以可以转换,这边是转换就像 int64 ->int , 是不是就是 int(int64) 这种写法, 那么 greet 就是 english ,然后调用了 say ('world')=>greet(n) 方法, 实际上就是 english('world)
    Rache1
        46
    Rache1  
       261 天前
    就是存了个回调函数吧,等效于 JS 的。


    function Foo (func) {
    this._func = func;

    this.say = function (str) {
    console.log(this._func(str));
    };

    if (!(this instanceof Foo)) {
    return new Foo(func);
    }
    }

    function english (str) {
    return 'Hello, ' + str;
    }

    // 1
    Foo(english).say('World');

    // 2
    (new Foo(english)).say('World');
    zhangyq008
        47
    zhangyq008  
       261 天前
    类似接口型函数,用处多多
    看下这篇文章 https://geektutu.com/post/7days-golang-q1.html
    hb751968840
        48
    hb751968840  
       261 天前
    js 好理解
    function english(name) {
    return "Hello, " + name
    }
    function Greeting (english) {
    return {
    say: (name) => {
    english(name)
    }
    }
    }
    function main() {
    const greet = Greeting(english)
    greet.say("World")
    }
    largezhou
        49
    largezhou  
       261 天前
    首先 Greeting 的类型是一个函数
    然后 english 这个函数跟 Greeting 的函数签名一样
    所以可以把 english 这个函数,强转成 Greeting 类型,即:greet := Greeting(english)
    然后调用 Greeting 的“实例”方法 say
    由于 Greeting 本身是个函数,可以直接调用,say 里面就是调用 g(n)
    实际就是调用的 english("World")
    Vegetable
        50
    Vegetable  
       261 天前
    写成这样同事不打人的吗
    svnware
        51
    svnware  
       261 天前
    学过 C/C++的,一看就明白
    sztink
        52
    sztink  
       261 天前
    **方法本质就是普通的函数,方法的接收者就是隐含的第一个参数。**

    ```go
    greet.say("World");
    say(greet, "World");
    ```
    上面两者是等效的,从这个角度理解就简单了不少。具体介绍可以看:[深入 Go 语言之旅: 方法]( https://go.cyub.vip/function/method/)
    via
        53
    via  
       261 天前   ❤️ 1
    鸭子类型,english 入餐像鸭子,出餐也像鸭子,那我们就可以认为 English 就是鸭子。

    上面的鸭子就是 Greeting
    via
        54
    via  
       261 天前
    OP 可以断言下:english.(Greeting) 看看会不会报错
    sztink
        55
    sztink  
       261 天前
    @sztink 回复咋不支持 markdown 格式,另外怎么发链接,我发的链接都显示成纯文本(没使用 markdown ,直接发个链接也这样),看别人发的可以直接点击?有 V 友可以告知怎么操作链接吗。
    FengMubai
        56
    FengMubai  
       261 天前
    你在`return "Hello, " + name`处打个断点, 观察调用栈能就明白
    cosiner
        57
    cosiner  
       261 天前
    type 类型名称 类型定义

    type Greeting func(name string)
    type Name string
    type Data struct {
    Name string
    }

    type 类型名称 类型名称
    type Data2 Data


    Go 里面自己使用 type 定义的新类型名称都可以给它加方法,
    你这个例子里面是把 english 相当于是匿名的 func(string)类型, 然后把它转换成了 Greeting 类型(实际都是 func(string)类型,Go 里面能随便转换), 就可以调用 Greeting 的 say 方法了,和接口,鸭子类型没关系
    wqtacc
        58
    wqtacc  
       261 天前
    ```go
    package main

    import "fmt"

    // Greeting 定义为一个函数
    type Greeting func(name string) string

    func (g Greeting) say(n string) {
    // g 调用函数自身
    s := g(n)
    fmt.Println(s)
    }

    func english(name string) string {
    return "Hello, " + name
    }

    func main() {
    // english 函数与 Greeting 签名相同,进行强转
    greet := Greeting(english)
    greet.say("World")


    var greet2 Greeting = func(s string) string {
    return "Hello, " + s
    }
    fmt.Println(greet2("World!"))
    greet2.say("World!")
    }

    ```
    wqtacc
        59
    wqtacc  
       261 天前
    ```go
    package main

    import "fmt"

    // Greeting 定义为一个函数
    type Greeting func(name string) string

    func (g Greeting) say(n string) {
    // g 调用函数自身
    s := g(n)
    fmt.Println(s)
    }

    func english(name string) string {
    return "Hello, " + name
    }

    func main() {
    // english 函数与 Greeting 签名相同,进行强转
    greet := Greeting(english)
    greet.say("World")

    // 完整的长生命方法, 同时去除 english 的干扰项
    var greet2 Greeting = func(s string) string {
    return "Hello, " + s
    }
    fmt.Println(greet2("World!"))
    greet2.say("World!")
    }

    ```
    yuzo555
        60
    yuzo555  
       261 天前
    Claude 3 的解释:
    这段代码的输出 "Hello, World" 是因为以下几个原因:

    1. 在代码中定义了一个名为 Greeting 的函数类型,它是一个接受字符串参数,并返回字符串的函数。

    2. 定义了一个名为 english 的函数,它满足 Greeting 类型的要求,即接受一个字符串参数,并返回一个字符串 "Hello, " 加上传入的参数。

    3. 在 main 函数中,创建了一个名为 greet 的变量,它的类型是 Greeting,并将 english 函数转换为 Greeting 类型赋值给了 greet 。

    4. 对于 Greeting 类型的变量,Go 语言会自动为其提供一个 say 方法,这个方法接受一个字符串参数,并调用 Greeting 类型的函数,传入该字符串参数。

    5. 在 main 函数中,调用了 greet.say("World")。这相当于先调用了 greet("World"),也就是调用了 english("World")函数,该函数返回了 "Hello, World"。然后将这个返回值作为参数打印出来。

    所以,最终输出的就是 "Hello, World"。这段代码展示了 Go 语言中将函数作为值进行传递和赋值的能力,以及通过为某个类型定义方法来扩展其功能的特性。

    在这段代码中,比较难以理解的一个点是:

    func (g Greeting) say(n string) {
    fmt.Println(g(n))
    }
    这里为 Greeting 类型(它是一个函数类型)定义了一个 say 方法。

    具体来说,有以下几点需要注意:

    1. Greeting 是一个函数类型,它本身并不是一个具体的函数值,而是一种函数签名的描述。

    2. Go 语言允许为任何命名类型(包括内置类型和自定义类型)定义方法,这里是为 Greeting 这个函数类型定义了一个 say 方法。

    3. say 方法的接收者是 g Greeting,表示这个方法会为所有 Greeting 类型的值(函数值)提供 say 方法。

    4. 在 say 方法的实现中,它直接以调用函数的方式 g(n) 来执行该 Greeting 类型的函数值,并将结果打印出来。

    这种为函数类型定义方法的做法,看起来有点违反直觉,因为我们一般会认为方法只能为结构体或对象这样的数据类型定义。但在 Go 语言中,函数作为一等公民,也可以为其定义方法。

    这样做的好处是,可以为某个函数类型扩展一些通用的辅助方法,而不需要为每个具体的函数值都实现这些方法。这提高了代码的可重用性和可维护性。
    2kCS5c0b0ITXE5k2
        61
    2kCS5c0b0ITXE5k2  
       261 天前
    类型别名
    Subfire
        62
    Subfire  
       261 天前

    greet := Greeting(english)
    greet.say("World")
    换成
    var greet2 Greeting = english
    greet2.say("World")
    是不是更容易理解点了?
    Jessec
        63
    Jessec  
       261 天前
    我一开始也蒙,仔细观察发现这一行代码实际上是把 english 的类型转换成了 Greeting
    greet := Greeting(english)
    lilei2023
        64
    lilei2023  
       260 天前
    貌似就是转换了一下类型,然后把函数作为参数传进去了,前端不经常这么干么??
    lilei2023
        65
    lilei2023  
       260 天前
    @lilei2023 更正一下,没传!看错了!
    sunzhenyucn
        66
    sunzhenyucn  
       260 天前
    这样写不是闲的吗?同事应该会打人吧(逃
    sztink
        67
    sztink  
       258 天前
    @sunzhenyucn 很常见的一个技巧,经常用来将一个普通函数包裹成实现特定接口。比如上面就是 english 这个普通函数可以变成实现 say 方法的接口对象。Go 源码 net/http 里面有使用到 https://github.com/golang/go/blob/master/src/net/http/server.go#L2158-L2167 。包 robfig/cron 里面的 https://github.com/robfig/cron/blob/master/cron.go#L133-L154
    sunzhenyucn
        68
    sunzhenyucn  
       254 天前
    @sztink 学到了 谢谢
    kenilalexandra
        69
    kenilalexandra  
       240 天前
    感觉并没有多难理解,Greeting 类型接收函数作为参数,say 函数由于是继承了 Greeting 的所有 greet 实际可以直接调用 say 函数不就完事了?难道说是你没理解函数也可以当作变量进行传递还是?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2645 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 03:59 · PVG 11:59 · LAX 19:59 · JFK 22:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.