Still new to Julia and trying to test one of my methods which return a Dict
. I know what should be included in this Dict
and I try to compare it with what is returned by my method. However I have difficulties to test equality between two Dicts.
Here is a minimal working example of my problem :
Module.jl
module Module
import Base
export Struct
export Simple
struct Struct
id::Int
x::Vector{Float64}
end
struct Simple
id::Int
end
end
runtests.jl
import Base
using Module
using Test
@testset "simple" begin
expected = Dict{Int, Module.Simple}()
expected[1] = Module.Simple(1)
actual = Dict{Int, Module.Simple}()
actual[1] = Module.Simple(1)
@test actual == expected
end
@testset "struct" begin
expected = Dict{Int, Module.Struct}()
expected[1] = Module.Struct(1, [1.0])
actual = Dict{Int, Module.Struct}()
actual[1] = Module.Struct(1, [1.0])
@test actual == expected
end
For the Simple
struct, test passed without any problem.
For the Struct
struct, test failed with the following error :
(Module) pkg> test
Testing Module
Resolving package versions...
Test Summary: | Pass Total
simple | 1 1
struct: Test Failed at /home/xxxxxxx/sandbox/Module/test/runtests.jl:18
Expression: actual == expected
Evaluated: Dict(1 => Struct(1, [1.0])) == Dict(1 => Struct(1, [1.0]))
Stacktrace:
[1] top-level scope at /home/xxxxxxxxx/sandbox/Module/test/runtests.jl:18
[2] top-level scope at /build/julia/src/julia-1.2.0/usr/share/julia/stdlib/v1.2/Test/src/Test.jl:1113
[3] top-level scope at /home/xxxxxxxx/sandbox/Module/test/runtests.jl:14
Test Summary: | Fail Total
struct | 1 1
First, I have strictly no idea why this error occurs. Is it related to the fact that Simple
is strictly immutable and Struct
is not (Vector is an array and arrays are mutable in julia if i get it right) ?
I try to find an answer in julia documentation and discourse and I thought :
- I should create following methods :
Base.isequal(struct::Struct)
and Base.hash(struct::Struct)
- I should replace
Struct
variables by strictly immutable types
So I have the following questions :
- Why this error occurs ?
- Should I create a specific
Base.isequal/hash
for each new struct I create ?
- Is there any way to rely on default
Dict
implementation to test equality between two dicts with custom composite types ?
Thank you!
What you are running into is that ==
for custom immutable structs just ends up falling back to ===
, and because your two arrays you pass to Struct
don’t have the same location in memory, this returns false. There is already an open issue for this here. The solution in your case would be to overload Base.:(==)
or Base.isequal
for Struct
. You might not even need to overload hash
, but I would test that again first.
1 Like
Thanks for your answer and the link to the issue.
I understand now why for Struct
it does not work and throw an error.
Just have a follow up question for the Simple
case :
@testset "identity/equals" begin
a = 1
b = 1
@test 1 === 1 # pass => OK
@test [1.0] === [1.0] # fail => OK
@test [1.0] == [1.0] # pass => OK
@test a === b # pass => Why ?
end
In this test a
and b
should not have the same location in memory and the identity comparison still return true. Is there a special case for primitive types ?
There is no special case for primitive types, in julia, the variables use the memory used to store its value, each variable does not have its own memory. So a and b have the same location, and it is normal that pass the identity comparison. When you say var1 = var2, var1 and var2 are alias, each variable has not its own memory, the memory is the same, the memory needed to store the value.
b = [1.0]
a = b
@test a === b # true
Actually, when the variable value is scalar, its behavior is similar to have its own memory.
a = 4
b = a # Both are the same value and memory position
a === b # true
a = 5 # Now a make reference to constant 5, stored in a different memory position
a === b # false
println(b) # Print 4
println(a) # Print 5
2 Likes
By the way, as @simeonschaub said, your code will pass the tests doing:
import Base: ==
function ==(val1::Module.Struct, val2::Module.Struct)
return val1.id == val2.id && val1.x == val2.x
end
1 Like
Thank you for your answer.
I need more insight :
I quote :
So a and b have the same location, and it is normal that pass the identity comparison
a = 1
b = 1
a === b # true
why in this case a
and b
refers to the same memory location ?
Thank you !
It doesn’t. Forget about memory locations, they are very misleading in understanding ===
.
The best definition is in its docstring:
Determine whether x
and y
are identical, in the sense that no program could distinguish them.
Also, if you end up defining an ==
method for something, it is usually advantageous to define Base.hash
too right away. It is very easy to end up with your values in a Dict
or something similar, and then chase a silly bug for hours just because hash
does not match ==
.
2 Likes
Thank you for your answer.
Ok I had to focus more on ===
documentation.
Indeed integer literals 1
could not be distinguished.
Then, how julia distinguish arrays (first guess : allocated on the heap at different location? )
a = [1.0]
b = [1.0]
a === b # false
I agree with you both ==
and hash
should be overloaded right away.
One key ingredient about the efficiency is Julia is that the “how” is left unspecified, so it leaves considerable room for the compiler to do clever things. So unless you want to understand internals (in which case dig into the source and follow the devdocs), don’t be concerned about this.
What is clear that one could devise a conforming program could distinguish them (eg change some values and check if they change in the other one), so they are not equal. Whereas eg
using StaticArrays
a = SVector(1, 2)
b = SVector(1, 2)
a === b # true
2 Likes