Skip to content
This repository has been archived by the owner on Aug 18, 2023. It is now read-only.

Julia macros to automatically define hash and == for struct types.

License

Notifications You must be signed in to change notification settings

JuliaServices/AutoHashEqualsCached.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This package has been archived. Please use https://github.com/JuliaServices/AutoHashEquals.jl instead!

Build Status Coverage

AutoHashEqualsCached

A pair of macros to add == and hash() to struct types: @auto_hash_equals and @auto_hash_equals_cached.

@auto_hash_equals

The macro @auto_hash_equals produces an implementation of Base.hash(x) that computes the hash code when invoked. It is offered for compatibility with the package AutoHashEquals.

You use it like so:

@auto_hash_equals struct Box
    x
end

which is translated to

struct Box
    x
end
Base.hash(x::Type, h::UInt) = hash(x.x, hash(:Foo, h))
Base.(:(==))(a::Box, b::Box) = isequal(a.x, b.x)

However, there are a few enhancements beyond what is provided by AutoHashEquals:

  • It handles empty structs without error
  • You can use it either before or after another macro.
  • You can specify the hash function to be implemented, by naming it before the struct definition:
@auto_hash_equals SomePackage.myhash struct Foo
    x
    y
end

In this case the macro implements both SomePackage.myhash and Base.hash for Foo.`

For compatibility with AutoHashEquals, we do not take the type arguments of a generic type into account in ==. So a Box{Int}(1) will test equal to a Box{Any}(1).

@auto_hash_equals_cached

The macro @auto_hash_equals_cached is useful for non-mutable struct types that define recursive or deep data structures (and therefore are likely to be stored on the heap). It computes the hash code during construction and caches it in a field of the struct. If you are working with data structures of any significant depth, computing the hash once can speed things up at the expense of one additional field per struct.

You use it like so:

@auto_hash_equals_cached struct Point{T<:Any, U<:Any}
  x::T
  y::U
end

which is translated to

struct Point{T<:Any, U<:Any}
    x::T
    y::U
    _cached_hash::UInt
    Point{T,U}(x::T, y::U) where {T<:Any, U<:Any} = new{T,U}(x,y,hash(y, hash(x, hash(Point{T,U}))))
end
Base.hash(x::Point) = x._cached_hash
Base.hash(x::Point, h::UInt) = hash(x._cached_hash, h)
function Base.:(==)(a::Point{T,U}, b::Point{T,U}) where {T<:Any, U<:Any}
    a._cached_hash == b._cached_hash && isequal(a.x, b.x) && isequal(a.y, b.y)
end
Base._show_default(io::IO, x::Point) = AutoHashEqualsCached._show_default_auto_hash_equals_cached(io, x)
Point(x::T, y::U) where {T<:Any, U<:Any} = Point{T, U}(x, y)

We use isequal so that a floating-point NaN compares equal to itself. The generated code checks the exact types of the two compared objects, so Box{Int}(1) will compare unequal to Box{Any}(1).

The definition of _show_default(io,x) prevents display of the _cached_hash field while preserving the behavior of Base.show(...) that handles self-recursive data structures without a stack overflow.

We provide an external constructor for generic types so that you get the same type inference behavior you would get in the absence of this macro. Specifically, you can write Point(1, 2) to get an object of type Point{Int, Int}.

As with @auto_hash_equals, you can specify the hash function to be implemented:

@auto_hash_equals_cached SomePackage.myhash struct Foo
    x
    y
end

In this case the macro implements both SomePackage.myhash and Base.hash for Foo.`

About

Julia macros to automatically define hash and == for struct types.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages