# Passing method arguments: Tuple vs Dict

What am I missing?

``````"""
A struct to hold parameters
"""
struct P
a :: Float64
b :: Vector{Float64}
end

"""
A method to update the parameters
"""
function P(; a = 0.0, b = [0.0, 0.0])
P(a, b)
end
``````

Create an instance of P by passing the parameters directly

``````julia> p0 = P(a = 1.0, b = [2.0, 3.0])
P(1.0, [2.0, 3.0])
``````

Create an instance of P by passing the parameters via a dictionary

``````julia> d1 = Dict(:a => 1.0, :b => [2.0, 3.0])
julia> p1 = P(; d1...)
P(1.0, [2.0, 3.0])
``````

Isn’t that the same thing? Ahem, no:

``````julia> p1 == p0
false
``````

And yet they have the same type:

``````julia> typeof(p0) == typeof(p1)
true
julia> fieldnames(typeof(p0)) == fieldnames(typeof(p1))
true
``````

So what’s the difference? Thanks.

1 Like

By default, two structs are considered equal if they are bitwise the same. In your example, this is not the case because the two `Vector`s are different vectors (even though they have the same entries). The following example should make this clear:

``````julia> struct Foo
a::Vector{Int}
end

julia> Foo([1,2]) == Foo([1,2]) # Different vectors with same entries
false

julia> a = [1,2]; Foo(a) == Foo(a)  # Same vector
true
``````

If this is not the behaviour that you want, then you must define your own equality comparison:

``````julia> Base.:(==)(a::Foo,b::Foo) = a.a == b.a

julia> Foo([1,2]) == Foo([1,2])
true  # <- I guess this is what you wanted?
``````
9 Likes

Thanks ettersi. I was just in the process of checking that I could pass the same data as a dictionary. I did not encounter this issue with scalars:

``````struct Foo
a::Int64
end
julia> Foo(1) == Foo(1)
true
``````

Why the difference between scalars and vectors? Thanks!

The key difference is mutable vs immutable data structures. Scalars are immutable: if you have two variables `a = b = 1`, then regardless what you do to `a` you will not be able to change the value of `b`. `Vector`s on the other hand are mutable: if you do `a = b = [1]` and `a[1] = 2`, then both `a` and `b` will now point to a vector `[2]`. Given this difference, it makes sense to make the default such that `Foo(1) == Foo(1)`, but `Foo([1]) != Foo([1])`.

3 Likes

Makes perfect sense. Great answer, thanks ettersi!

1 Like

And don’t forget your custom `Base.hash` if you use it anywhere as a key (eg `Dict`).

1 Like

Thanks Tamas. Are you suggesting to overload `Base.hash` to assign a hash-key to each key,value pair inside the dictionary and `Base.isequal` to return true if all the hash-keys match? Am I understanding? Do you have a ready-made example of use?

I’ve just noticed this behaviour:

``````struct Foo
a :: Real
end

julia> Foo(1) == Foo(1)
true

julia> Foo(1) == Foo(1.)
false
``````

By contrast,

``````julia> 1 == 1.
true
``````

(but, as expected,

``````    julia> 1 === 1.
false
``````

).

The docs here and here are very clear about what `==` does and also the difference with `isequal`. Before reading the docs, I expected either `false` or a `MethodError`.

No, the point about `hash` is that if you define equality for your struct, then you should also define the way the struct itself is hashed so that `isequal(x::Foo, y::Foo)` implies `hash(x) == hash(y)`, as specified here: Essentials · The Julia Language

In most cases this is extremely simple. Something like:

``````function Base.hash(x::Foo, h::UInt)
hash(x.a, h)
end
``````

would work just fine.

2 Likes

Thanks rdeits. I’m not familiar with any of this.

I want this to work for any number, so I type with `Number`, right?

``````struct Foo
a::Vector{Number}
end
``````

On the one hand:

``````julia> Base.isequal(x::Foo, y::Foo) = x.a == y.a
julia> Foo([1]) == Foo([1])
false
``````

On the other hand:

``````julia> Base.:(==)(x::Foo, y::Foo) = x.a == y.a
julia> Foo([1]) == Foo([1])
true
``````

So there’s a subtle difference between `==` and `isequal`.

And what is this for?

``````julia> Base.hash(x::Foo, h::UInt) = hash(x.a, h)

julia> hash(Foo([1]))
0x8c5e337b3aca66f1

julia> hash(Foo([1])) == hash(Foo([1]))
false
``````

So while now `Foo([1]) == Foo([1]) ` has become `true`, we have `hash(Foo([1])) == hash(Foo([1]))` is `false`. What do I do now?

What I gather from the docs is that if you create your own definition of equality between two things, you would also make sure that the two things are mapped to the same hash. As for what a hash is, I found this definition:

A hash is a function that converts one value to another. […] hashes are used to index data. Hashing values can be used to map data to individual “buckets” within a hash table. Each bucket has a unique ID that serves as a pointer to the original data. This creates an index that is significantly smaller than the original data, allowing the values to be searched and accessed more efficiently. (source)

The docs are short enough to be quoted in full:

Base.hash — Function

`````` hash(x[, h::UInt])

Compute an integer hash code such that `isequal(x,y)` implies `hash(x)==hash(y)`. The optional second argument `h` is a hash code to be mixed with the result.

New types should implement the 2-argument form, typically by calling the 2-argument `hash` method recursively in order to mix hashes of the contents with each other (and with `h`). Typically, any type that implements `hash` should also implement its own `==` (hence `isequal`) to guarantee the property mentioned above. Types supporting subtraction (operator `-`) should also implement [`widen`](@ref), which is required to hash values inside heterogeneous arrays.``````

I would recommend

``````struct Foo{T <: Number}
a::Vector{T}
end
``````

If you don’t understand the relative pros and cons of your proposal and mine, then I would recommend you open another topic since this issue is orthogonal to the main issue of this topic.

Have a look at the docstrings for `==` and `isequal`. The gist is that you should define only `==` unless you know what you are doing.

Hash functions are a very subtle topic, which is why I did not mention `Base.hash` initially. If you really want to know all the details about this, then I recommend you have a look at a couple of references online until you find one that works for you. If not, just go with what you proposed:

After defining this method, I get

``````julia> hash(Foo([1])) == hash(Foo([1]))
true
``````

so I believe you obtaining

must be a consequence of some outdated method definitions. Try restarting your Julia REPL and repeating your experiment.

3 Likes

I have already accepted your answer from all the way up this thread. Thanks! I was initially only interested in understanding the behaviour of `==`, with no plans to change its behaviour. I’m sure there are good reasons it behaves this way, and I have no plan to touch it. (If I’m hit by another unexpected `false`, I won’t care so much and move on)

Having said that, further down the discussion Tamas and Robin mentioned `hash`, which piqued my curiosity, as I’ve seen that come up in code once in a while (and the “don’t forget” made it sound possibly important). I thought perhaps this is the day I finally understand what it is for. But maybe not.

I did have a look at the docs for `==` and `isequal` before posting, but the reason for the different behaviour did not jump out. The main difference stated in the doc is the treatment of floating point numbers and missing values, but there are none here, so I don’t know what’s going on. Here’s a quote from the doc:

Similar to `==` , except for the treatment of floating point numbers and of missing values.

As for the last point, you’re absolutely right, I messed up somewhere, it does work as expected: