Chaining functions in Go?

huangapple go评论90阅读模式
英文:

Chaining functions in Go?

问题

我尝试了这样做:

package main

import (
    "fmt"
    "strings"
)

type String string


func (s *String) tolower() String {
    *s = String(strings.ToLower(string(*s)))
    return *s
}

func (s *String) toupper() String {
    *s = String(strings.ToUpper(string(*s)))
    return *s
}

func main() {
    var s String = "ASDF"
    (s.tolower()).toupper() // 这里出错了
    // s.toupper();s.tolower(); // 这样是可以的
    // s.tolower().toupper() // 这样也会出错
    fmt.Println(s)
}

但是我得到了以下错误:

prog.go:30: cannot call pointer method on s.tolower()
prog.go:30: cannot take the address of s.tolower()

程序退出。

为什么我不能让这个链式调用起作用?

英文:

I tried doing this:

package main

import (
    "fmt"
    "strings"
)

type String string


func (s *String) tolower() String {
    *s = String(strings.ToLower(string(*s)))
    return *s
}

func (s *String) toupper() String {
    *s = String(strings.ToUpper(string(*s)))
    return *s
}

func main() {
    var s String = "ASDF"
    (s.tolower()).toupper() // this fails
    // s.toupper();s.tolower(); // this works
    // s.tolower().toupper() // this fails too
    fmt.Println(s)
}

But I got these errors:

prog.go:30: cannot call pointer method on s.tolower()
prog.go:30: cannot take the address of s.tolower()

Program exited.

Why can't I make this chain work?

答案1

得分: 17

这段代码是可以工作的:

package main

import (
    "fmt"
    "strings"
)

type String string


func (s *String) tolower() *String {
    *s = String(strings.ToLower(string(*s)))
    return s
}

func (s *String) toupper() *String {
    *s = String(strings.ToUpper(string(*s)))
    return s
}

func main() {
    var s String = "ASDF"
    (s.tolower()).toupper()
    s.toupper();
    s.tolower();
    s.tolower().toupper()
    fmt.Println(s)
}

你的返回类型是String,用于指向String的指针上定义的函数。将它们链接起来是没有意义的。

英文:

This works:

package main

import (
    "fmt"
    "strings"
)

type String string


func (s *String) tolower() *String {
    *s = String(strings.ToLower(string(*s)))
    return s
}

func (s *String) toupper() *String {
    *s = String(strings.ToUpper(string(*s)))
    return s
}

func main() {
    var s String = "ASDF"
    (s.tolower()).toupper()
    s.toupper();
    s.tolower();
    s.tolower().toupper()
    fmt.Println(s)
}

Your return type is of String, for functions defined on pointers to String. It wouldn't make sense to be able to chain them.

答案2

得分: 5

tolower()和toupper()的接收者是指向String的指针,但它们返回的是String(而不是指向String的指针)。

你可以通过改变其中一个来修复这个问题。

例如,将函数的签名改为以下之一:

func (s *String) toupper() *String

或者

func (s String) toupper() String

(参考:http://play.golang.org/p/FaCD8AQtIX)

英文:

tolower() and toupper() have pointer-to-String as the receivers, but they are returning String (not pointer-to-String).

You can fix this by changing one or the other.

e.g. change the signature of the function to either:

func (s *String) toupper() *String

or

func (s String) toupper() String

(see: http://play.golang.org/p/FaCD8AQtIX)

答案3

得分: 3

当你在一个变量上调用一个带有指针接收器的方法时(在你的例子中是s),那么该值的地址将会自动被取出。所以,你基本上是在调用(&s).toupper()。这个机制适用于所有可寻址的值。

函数的返回值是不可寻址的,除非你将它们存储在一个变量中(这样它们就有了在当前堆栈帧或堆中的永久位置)。

我建议使用以下的API,因为它看起来用户应该使用String而不是*String来操作你的字符串类型。因此,设计一个一致的API也使用String是有意义的,以避免混淆。通过值传递字符串本身非常快,因为它们在内部被实现为指向不可变数组的指针:

func (s String) tolower() String {
    return String(strings.ToLower(string(s)))
}

这个方法不需要一个指针接收器,因为它不修改当前的字符串。它返回一个新的字符串。你也可以很容易地链式调用这些方法。

另外,你可以这样实现这些方法:

func (s *String) tolower() *String {
    *s = String(strings.ToLower(string(*s)))
    return s
}

在这种情况下,你保持返回相同的指针。所以,为了调用(s.tolower()).toupper(),你需要能够取出s的地址,这是可能的,因为你已经将它赋值给一个变量。然后,链中的所有后续方法调用也是可能的,因为你用一个指向初始变量的指针来调用它们。这与你尝试链式调用方法的方式不同,每个方法调用必须取出一个临时变量的地址以便修改它(这并不是很有用)。

英文:

When you call a method with a pointer receiver on a variable (s in your example), then an address of that value will be taken automatically. So, you are basically calling (&s).toupper(). This mechanism works for all values that are addressable.

Return values of functions are not addressable unless you store them in a variable (so that they have a permanent place in the current stack frame or the heap).

I would recommend the following API, because it looks like the user of your string type is supposed to work with String and not *String. Therefore it makes sense to design a consistent API that also uses String to avoid confusion. Passing a string by value is extremely fast anyway, because they are implemented as pointers to immutable arrays internally:

func (s String) tolower() String {
    return String(strings.ToLower(string(s)))
}

This method does not need a pointer receiver, because it doesn't modify the current string. It returns a new string instead. You can also easily chain those methods.

Alternatively, you can implement the methods this way:

func (s *String) tolower() *String {
    *s = String(strings.ToLower(string(*s)))
    return s
}

In this case, you keep returning the same pointer. So, in order to call (s.tolower()).toupper() you need to be able to take the address of s which is possible since you have assigned it to a variable. Then all further method calls in the chain are also possible, because you call them with a pointer to your initial variable. This differs from your attempt of chaining methods were each method call must have taken the address of a temporary variable in order to modify it (which isn't very useful).

答案4

得分: 0

也许你可以尝试这个项目:https://github.com/Laisky/go-chaining

import "github.com/Laisky/go-chaining"

func toLower(c *chaining.Chain) (interface{}, error) {
    v := c.GetString()
    return strings.ToLower(v), nil
}

func toUpper(c *chaining.Chain) (interface{}, error) {
    v := c.GetString()
    return strings.ToUpper(v), nil
}

func TestChainWithString(t *testing.T) {
    f := func() (string, error) { return "aBcD", nil }
    r := chaining.New(f()).
        Next(toLower).
        Next(toUpper)

    expectVal := "ABCD"
    if r.GetString() != expectVal {
        t.Errorf("expect %v, got %v", expectVal, r.GetString())
    }
}

这段代码使用了github.com/Laisky/go-chaining库,其中定义了两个函数toLowertoUpper,分别用于将字符串转换为小写和大写。在TestChainWithString函数中,首先定义了一个返回字符串的函数f,然后使用chaining.New创建了一个链式调用的对象r,通过Next方法依次将toLowertoUpper函数添加到链中。最后,通过GetString方法获取链式调用的结果,并与预期值进行比较,如果不相等则输出错误信息。

英文:

maybe you can try this project: https://github.com/Laisky/go-chaining

import "github.com/Laisky/go-chaining"

func toLower(c *chaining.Chain) (interface{}, error) {
    v := c.GetString()
    return strings.ToLower(v), nil
}

func toUpper(c *chaining.Chain) (interface{}, error) {
    v := c.GetString()
    return strings.ToUpper(v), nil
}

func TestChainWithString(t *testing.T) {
    f := func() (string, error) { return "aBcD", nil }
    r := chaining.New(f()).
        Next(toLower).
        Next(toUpper)

    expectVal := "ABCD"
    if r.GetString() != expectVal {
        t.Errorf("expect %v, got %v", expectVal, r.GetString())
    }
}

huangapple
  • 本文由 发表于 2013年8月10日 01:31:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/18152419.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定