英文:
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
答案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
库,其中定义了两个函数toLower
和toUpper
,分别用于将字符串转换为小写和大写。在TestChainWithString
函数中,首先定义了一个返回字符串的函数f
,然后使用chaining.New
创建了一个链式调用的对象r
,通过Next
方法依次将toLower
和toUpper
函数添加到链中。最后,通过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())
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论