In the following example foo and bar are basically of the same type: map[uint32]string.
Nevertheless go1.18beta complains that: M2 does not match map[K]V.
Is it even possible to get equal to accept both of these maps? Do I need to change the signature of equal or the declarations of the maps itself?
package main
import "fmt"
func equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}
type (
someNumericID uint32
someStringID string
)
func main() {
foo := map[uint32]string{
10: "bar",
}
bar := map[someNumericID]someStringID{
10: "bar",
}
if equal(foo, bar) == true {
fmt.Println("Maps are the same")
} else {
fmt.Println("Maps are not the same")
}
}
CodePudding user response:
Is it even possible to get equal to accept both of these maps?
Yes, but you have to differentiate the key and value types, as they are not the same. This is what the fixed function could look like:
func equal[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 {
return false
}
}
return true
}
In particular, both keys and value type parameters are constrained to the respective approximate elements ~uint32 and ~string in order to allow the conversions m2[K2(k)] and V2(v1) in the function body. This is required to compare values (including map indexing) that don't have the same type, but have the same underlying type.
The above solution forgoes type parameters M1 and M2 on the map types — due to what appears to be a compiler bug; see comments for details —, but since you are not actually using those types in the function body, nor in return values, they are not strictly needed.
Playground: https://gotipplay.golang.org/p/Y8C_8ilsXUg
If you want to understand why your first example failed, here's a breakdown. The relevant paragraph in the language specs is Type inference.
- In
equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2), the type paramsM1andM2have the same constraint~map[K]V. - When you call the function without explicit instantiation, the compiler tries to infer the type parameters from the types of the supplied arguments. In short, it infers
KandVfromM1, soequal(foo, bar)wherefooismap[uint32]stringresults inK = uint32andV = string. - The instantiated constraint is then
M1, M2 ~map[uint32]string - Now there's no more type params to infer, so it just type-checks
baragainst the instantiated constraint. Is the underlying (~) type ofbarthe same asmap[uint32]string? No. Even if the underlying types of keys and vals are the same, the underlying type of the entire map is exactlymap[someNumericID]someStringID. - Instantiation of
equalwith argsfooandbarfails.
This becomes more apparent if instead of relying on type inference, you instantiate equal with explicit type args. By specifying just M1 and M2 (remember that they have the same constraint): equal[map[uint32]string, map[uint32]string](foo, bar) then bar obviously doesn't match.
