go-cmp icon indicating copy to clipboard operation
go-cmp copied to clipboard

Comparing contravariant types doesn't work as expected using Transformer

Open tamird opened this issue 4 years ago • 5 comments

package main

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
)

type intHolder struct {
	i int
}

func main() {
	got := []intHolder{
		{i: 1},
	}
	want := []int{1}
	fmt.Println(cmp.Diff(want, got, cmp.Transformer("myTransformer", func(h intHolder) int {
		return h.i
	})))
}

produces:

  interface{}(
- 	[]int{1},
+ 	[]main.intHolder{{i: 1}},
  )

https://play.golang.org/p/GQqAZmbYUD8

Digging into the implementation revealed effectively that Transform is skipped whenever the types being compared aren't identical.

The same thing happens when got and want are scalars rather than slices: https://play.golang.org/p/r2O2UKYRD-r

tamird avatar May 19 '21 17:05 tamird

In Go, a []T and []R are invariant types (i.e., []T is not a sub-type of []R, nor is []R a sub-type of []T assuming T is not the same type as R). See https://blog.merovius.de/2018/06/03/why-doesnt-go-have-variance-in.html

Given that []T and []R are invariant, the current behavior seems expected.

Note that cmp determined []int and []main.intHolder to be different values since the types are different before the cmp.Transformer even has a chance to trigger. Even if we could get past variance issues with slices, the cmp.Transformer still wouldn't trigger since it only does so if both types being compared are the same. See https://play.golang.org/p/toWjEf2zabm

dsnet avatar May 19 '21 23:05 dsnet

If you want to operate on both []int and []main.intHolder, you would need a transformer that operates on either of those two types and coverts it a []int.

See https://play.golang.org/p/iRvh9tTJk_q for example.

dsnet avatar May 19 '21 23:05 dsnet

Sorry, of course - invariant is the term.

Thanks for the example! That works for mapping both slice types to a single slice type. It would be nice to be able to do that using a transformer that operates on just an element. That's not possible, is it?

tamird avatar May 19 '21 23:05 tamird

Context: https://fuchsia-review.googlesource.com/c/fuchsia/+/532541

tamird avatar May 19 '21 23:05 tamird

It would be nice to be able to do that using a transformer that operates on just an element. That's not possible, is it?

Perhaps. The current semantic is that transformers only apply if both values are the same type, and deviating from that would probably be a breaking change.

Futhermore, I imagine there are thorny issues that would arise from an assymetric application of transformers. For example, the reporter indicates the use of transformers with a Inverse(funcName, ...) call (example). I don't know how an asymmetric application of transformers would be adequately represented in the diff.

dsnet avatar May 19 '21 23:05 dsnet