In GO when I use a struct as a key for a map, there is an unicity of the keys.
For example, the following code produce a map with only one key : map[{x 1}:1]
package main
import (
"fmt"
)
type MyT struct {
A string
B int
}
func main() {
dic := make(map[MyT]int)
for i := 1; i <= 10; i++ {
dic[MyT{"x", 1}] = 1
}
fmt.Println(dic)
}
// result : map[{x 1}:1]
I Tried to do the same in Julia and I had a strange surprise :
This Julia code, similar to the GO one, produces a dictionary whith 10 keys !
type MyT
A::String
B::Int64
end
dic = Dict{MyT, Int64}()
for i in 1:10
dic[MyT("x", 1)] = 1
end
println(dic)
# Dict(MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1)
println(keys(dic))
# MyT[MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1)]
So what I did wrong ?
Thank you @DanGetz for the solution ! :
immutable MyT # or struct MyT with julia > 0.6
A::String
B::Int64
end
dic = Dict{MyT, Int64}()
for i in 1:10
dic[MyT("x", 1)] = 1
end
println(dic) # Dict(MyT("x", 1)=>1)
println(keys(dic)) # MyT[MyT("x", 1)]
You did not do anything wrong. The difference between the languages is in how they choose to hash a struct when using it as a key in the map/Dict. In go, structs are hashed by their values rather than their pointer addresses. This allows programmers to more easily implement multidimensional maps by using structs rather than maps of maps. See this blog post for more info.
Reproducing Julia's Behavior in Go
To reproduce Julia's behavior in go, redefine the map to use a pointer to
MyT
as the key type:Here,
pdic
uses the pointer to aMyT
struct as its key type. Because eachMyT
created in the loop has a different memory address, the key will be different. This produces the output:You can play with this on play.golang.org. Unlike in Julia (see below), the way the map type is implemented go means you cannot specify a custom hashing function for a user-defined struct.
Reproducing Go's Behavior in Julia
Julia uses the function
Base.hash(::K, ::UInt)
to hash keys for itsDict{K,V}
type. While it doesn't explicitly say so in the documentation, the default hashing algorithm uses the output fromobject_id
, as you can see in the source code. To reproduce go's behavior in Julia, define a newhash
function for your type that hashes the values of the struct:Note that you should also define the == operator in the same way to guarantee
hash(x)==hash(y)
impliesisequal(x,y)
, as mentioned in the documentation.However, the easiest way to get Julia to act like go in your example is to redefine
MyT
asimmutable
. As an immutable type, Julia will hashMyT
by its value rather than itsobject_id
. As an example:Edit: Please refer to @StefanKarpinski's answer. The
Base.hash
function must return aUInt
for it to be a valid hash, so my example won't work. Also there's some funkiness regarding user defined types which involves recursion.The reason you get 10 different keys is due to the fact that Julia uses the
hash
function when determining the key to a dict. In this case, I'm guessing that it's using the address of the object in memory as the key for the dictionary. If you'd like to explicitly make(A,B)
the unique key, you'll need to override thehash
function for your particular type, with something like this:That will replicate the Go behavior, with only one item in the
Dict
.Here's the documentation to the
hash
function.Hope that helps!
Mutable values hash by identity in Julia, since without additional knowledge about what a type represents, one cannot know if two values with the same structure mean the same thing or not. Hashing mutable objects by value can be especially problematic if you mutate a value after using it as a dictionary key – this is not a problem when hashing by identity since the identity of a mutable object remains the same even when it is modified. On the other hand, it's perfectly safe to hash immutable objects by value – since they cannot be mutated, and accordingly that is the default behavior for immutable types. In the given example, if you make
MyT
immutable you will automatically get the behavior you're expecting:For a type holding a
String
and anInt
value that you want to use as a hash key, immutability is probably the right choice. In fact, immutability is the right choice more often than not, which is why the keywords introducing structural types has been change in 0.6 tostruct
for immutable structures andmutable struct
for mutable structures – on the principle that people will reach for the shorter, simpler name first, so that should be the better default choice – i.e. immutability.As @ntdef has written, you can change the hashing behavior of your type by overloading the
Base.hash
function. However, his definition is incorrect in a few respects (which is probably our fault for failing to document this more prominently and thoroughly):Base.hash
that you want to overload isBase.hash(::T, ::UInt)
.Base.hash(::T, ::UInt)
method must return aUInt
value.Base.hash
, you should also overloadBase.==
to match.So this would be a correct way to make your mutable type hash by value (new Julia session required to redefine
MyT
):This is kind of annoying to do manually, but the AutoHashEquals package automates this, taking the tedium out of it. All you need to do is prefix the
type
definition with the@auto_hash_equals
macro:Bottom line:
If you have a type that should have value-based equality and hashing, seriously consider making it immutable.
If your type really has to be mutable, then think hard about whether it's a good idea to use as a hash key.
If you really need to use a mutable type as a hash key with value-based equality and hashing semantics, use the
AutoHashEquals
package.