添加一个从未被调用的函数会改善行为吗?

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

Adding a func never called improves behavior?

问题

以下是翻译好的部分:

代码部分不要翻译,只返回翻译好的部分,不要有别的内容,不要回答我要翻译的问题。

下面的代码产生了不理想的输出:

> [20010101 20010102].

当取消注释String函数时,它会产生更好的输出(但不是我自己的实现):

> [{20010101 1.5} {20010102 2.5}]

然而,String函数从未被调用过。
我看到DateValue中的Date是匿名的,因此func (Date) String被DateValue使用。

所以我的问题是:

1)这是语言问题,fmt.Println的实现问题,还是其他问题?注意:如果我从:

func (*DateValue) String() string

切换到

func (DateValue) String() string

我的函数至少会被调用并引发恐慌。所以如果我真的想要调用我的方法,我可以这样做,但是假设DateValue是一个非常大的对象,我只想通过引用传递它。

2)在使用反射的Stringer和json编码等功能时,如何有效地混合匿名字段和功能?例如,为作为匿名字段使用的类型添加String或MarshalJSON方法可能会导致奇怪的行为(例如,只打印或编码整个部分)。

package main

import (
	"fmt"
	"time"
)

type Date int64

func (d Date) String() string {
	t := time.Unix(int64(d),0).UTC()
	return fmt.Sprintf("%04d%02d%02d", t.Year(), int(t.Month()), t.Day())
}

type DateValue struct {
	Date 
	Value float64
}

type OrderedValues []DateValue

/*
// ADD THIS BACK and note that this is never called but both pieces of
// DateValue are printed, whereas, without this only the date is printed
func (dv *DateValue) String() string {
	panic("Oops")
	return fmt.Sprintf("DV(%s,%f)", dv.Date, dv.Value )
}
*/

func main() {
	d1, d2 := Date(978307200),Date(978307200+24*60*60)
	ov1 := OrderedValues{{ d1, 1.5 }, { d2, 2.5 }}
	fmt.Println(ov1)
}
英文:

The code below produces undesirable

> [20010101 20010102].

When uncommenting the String func it produces better (but not my implementation):

> [{20010101 1.5} {20010102 2.5}]

However that String func is never called.
I see that Date in DateValue is anonymous and therefore func (Date) String is being used by DateValue.

So my questions are:

  1. Is this a language issue, a fmt.Println implementation issue, or
    something else? Note: if I switch from:

    func (*DateValue) String() string

to

func (DateValue) String() string

my function is at least called and panic ensues. So if I really want my method called I could do that, but assume DateValue is really a very large object which I only want to pass by reference.

  1. What is a good strategy for mixing anonymous fields with
    functionality like Stringer and json encoding that use reflection
    under the covers? For example adding a String or MarshalJSON method
    for a type that happens to be used as an anonymous field can cause
    strange behavior (like you only print or encode part of the whole).

    package main

    import (
    "fmt"
    "time"
    )

    type Date int64

    func (d Date) String() string {
    t := time.Unix(int64(d),0).UTC()
    return fmt.Sprintf("%04d%02d%02d", t.Year(), int(t.Month()), t.Day())
    }

    type DateValue struct {
    Date
    Value float64
    }

    type OrderedValues []DateValue

    /*
    // ADD THIS BACK and note that this is never called but both pieces of
    // DateValue are printed, whereas, without this only the date is printed
    func (dv *DateValue) String() string {
    panic("Oops")
    return fmt.Sprintf("DV(%s,%f)", dv.Date, dv.Value )
    }
    */

    func main() {
    d1, d2 := Date(978307200),Date(978307200+246060)
    ov1 := OrderedValues{{ d1, 1.5 }, { d2, 2.5 }}
    fmt.Println(ov1)
    }

答案1

得分: 1

这是因为你传递了一个 DateValues 的切片而不是 DateValue 的指针。由于你为 *DataValue 定义了 String 方法,所以 *DateValue 是满足 Stringer 接口的类型。这也阻止了 DateValue 通过其匿名的 Date 成员来满足 Stringer 接口,因为只能使用值类型(DateValue)或指针类型(*DateValue)中的一个来满足接口。因此,当 fmt.Println 打印切片的内容时,它发现元素不是 Stringers,于是使用默认的结构格式化而不是你定义的方法,得到 [{20010101 1.5} {20010102 2.5}]

你可以将 OrderedValues 改为 []*DateValue,或者定义 func (dv DateValue) String() string 而不是指针版本。

英文:

It's because you've passed in a slice of DateValues and not DateValue pointers. Since you've defined the String method for *DataValue, *DateValue is what fulfills the Stringer interface. This also prevents DateValue from fulfilling the Stringer interface via its anonymous Date member, because only one of either the value type (DateValue) or the pointer type (*DateValue) can be used to fulfill an interface. So, when fmt.Println is printing the contents of the slice, it sees that the elements are not Stringers, and uses the default struct formatting instead of the method you defined, giving [{20010101 1.5} {20010102 2.5}].

You can either make OrderedValues a []*DateValue or define func (dv DateValue) String() string instead of the pointer version.

答案2

得分: 0

根据@SteveM的说法,我将其简化为一个更简单的测试案例:

package main

import "fmt"

type Fooable interface {
  Foo()
}

type A int

func (a A) Foo() { }

type B struct {
  A
}

// 取消下面的方法注释,它将打印出false
//func (b *B) Foo() { }

func main() {
  var x interface{} = B{}
  _, ok := x.(Fooable)
  fmt.Println(ok) // 打印true
}

换句话说,当定义*BFoo方法时,Foo方法不是B的方法集的一部分。

从规范中的阅读来看,我没有看到清晰的解释发生了什么。最接近的部分似乎在选择器的章节中:

> 对于类型为T或*T的值x,其中T不是接口类型,x.f表示T中最浅深度的具有f的字段或方法。

我唯一能想到的解释是,当它在B中寻找最浅深度的Foo方法时,它也会考虑*B的方法,出于某种原因(即使我们考虑的是类型B而不是*B);而*B中的Foo确实比A中的Foo更浅,所以它将*B中的Foo作为候选;然后它发现*B中的Foo不起作用,因为它在*B中而不是B中,所以它完全删除了Foo(即使从A继承了一个有效的Foo)。

如果这确实是发生的情况,那么我同意原帖中的观点,即添加一个方法到*B会导致删除B中的一个方法,这是非常令人费解的。

也许对Go更熟悉的人可以澄清这一点。

英文:

Based on what @SteveM said, I distilled it to a simpler test case:

package main

import "fmt"

type Fooable interface {
  Foo()
}

type A int

func (a A) Foo() { }

type B struct {
  A
}

// Uncomment the following method and it will print false
//func (b *B) Foo() { }

func main() {
  var x interface{} = B{}
  _, ok := x.(Fooable)
  fmt.Println(ok) // prints true
}

In other words, the Foo method is not part of the method set of B when the Foo method for *B is defined.

From reading the spec, I don't see a clear explanation of what is happening. The closest part seems to be in the section on selectors:

> For a value x of type T or *T where T is not an interface type, x.f
> denotes the field or method at the shallowest depth in T where there
> is such an f.

The only way I can see this explaining what is going on is if when it is looking for a method Foo at shallowest depth in B, it takes into consideration the methods for *B too, for some reason (even though we are considering type B not *B); and the Foo in *B is indeed shallower than the Foo in A, so it takes that one as the candidate; and then it sees that that Foo doesn't work, since it's in *B and not B, so it gets rid of Foo altogether (even though there is a valid one inherited from A).

If this is indeed what is going on, then I agree with the OP in that this is very counter-intuitive that adding a method to *B would have the reverse consequence of removing a method from B.

Maybe someone more familiar with Go can clarify this.

huangapple
  • 本文由 发表于 2012年6月15日 03:00:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/11039652.html
匿名

发表评论

匿名网友

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

确定