Skip to content

Commit 0ebd13e

Browse files
author
Neal Gafter
authored
Update the contents of this entire package (#32)
from what used to be AutoHashEqualsCached.jl Also correct the uuid - it has been wrong for two years!
1 parent f643bdd commit 0ebd13e

File tree

15 files changed

+1218
-252
lines changed

15 files changed

+1218
-252
lines changed

.github/workflows/CI.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
on:
3+
push:
4+
branches:
5+
- main
6+
tags: ['*']
7+
pull_request:
8+
concurrency:
9+
# Skip intermediate builds: always.
10+
# Cancel intermediate builds: only if it is a pull request build.
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
13+
jobs:
14+
test:
15+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
16+
runs-on: ${{ matrix.os }}
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
version:
21+
- '1.6'
22+
- '1.7'
23+
- '1.8'
24+
- '1.9'
25+
- 'nightly'
26+
os:
27+
- ubuntu-latest
28+
arch:
29+
- x64
30+
steps:
31+
- uses: actions/checkout@v2
32+
- uses: julia-actions/setup-julia@v1
33+
with:
34+
version: ${{ matrix.version }}
35+
arch: ${{ matrix.arch }}
36+
- uses: julia-actions/cache@v1
37+
- uses: julia-actions/julia-buildpkg@v1
38+
- uses: julia-actions/julia-runtest@v1
39+
- uses: julia-actions/julia-processcoverage@v1
40+
- uses: codecov/codecov-action@v2
41+
with:
42+
files: lcov.info

.github/workflows/CompatHelper.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: CompatHelper
2+
on:
3+
schedule:
4+
- cron: 0 0 * * *
5+
workflow_dispatch:
6+
jobs:
7+
CompatHelper:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Pkg.add("CompatHelper")
11+
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
12+
- name: CompatHelper.main()
13+
env:
14+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15+
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
16+
run: julia -e 'using CompatHelper; CompatHelper.main()'

.github/workflows/TagBot.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
name: TagBot
22
on:
3-
schedule:
4-
- cron: 0 * * * *
3+
issue_comment:
4+
types:
5+
- created
6+
workflow_dispatch:
57
jobs:
68
TagBot:
9+
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
710
runs-on: ubuntu-latest
811
steps:
912
- uses: JuliaRegistries/TagBot@v1
1013
with:
1114
token: ${{ secrets.GITHUB_TOKEN }}
12-
15+
ssh: ${{ secrets.DOCUMENTER_KEY }}

.github/workflows/register.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: Register Package
2+
on:
3+
workflow_dispatch:
4+
inputs:
5+
version:
6+
description: Version to register or component to bump
7+
required: true
8+
jobs:
9+
register:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
steps:
14+
- uses: julia-actions/RegisterAction@latest
15+
with:
16+
token: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
*.jl.*.cov
12
*.jl.cov
23
*.jl.mem
3-
4-
/Manifest.toml
4+
Manifest.toml
5+
/docs/build/
6+
/tmp/*
7+
.vscode

.travis.yml

Lines changed: 0 additions & 16 deletions
This file was deleted.

LICENSE.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1+
# AutoHashEquals.jl - Automatically define hash and equals for Julia.
2+
13
The AutoHashEquals.jl package is licensed under the MIT "Expat" License:
24

3-
> Copyright (c) 2015: andrew cooke.
4-
>
5-
> Permission is hereby granted, free of charge, to any person obtaining
6-
> a copy of this software and associated documentation files (the
7-
> "Software"), to deal in the Software without restriction, including
8-
> without limitation the rights to use, copy, modify, merge, publish,
9-
> distribute, sublicense, and/or sell copies of the Software, and to
10-
> permit persons to whom the Software is furnished to do so, subject to
11-
> the following conditions:
12-
>
13-
> The above copyright notice and this permission notice shall be
14-
> included in all copies or substantial portions of the Software.
15-
>
16-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17-
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18-
> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19-
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20-
> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21-
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22-
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5+
Copyright (c) 2015-2023: andrew cooke, RelationalAI, Inc, and contributors.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.

Project.toml

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
name = "AutoHashEquals"
2-
uuid = "97026771-8bcd-44fb-b7eb-f644370d63d3"
3-
authors = ["andrew cooke <[email protected]>"]
2+
uuid = "15f4f7f2-30c1-5605-9d31-71845cf9641f"
3+
authors = ["Neal Gafter <[email protected]>", "andrew cooke <[email protected]>"]
44
version = "1.0.0"
55

6-
[compat]
7-
julia = "1"
8-
9-
[extras]
10-
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
11-
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
12-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
6+
[deps]
7+
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
138

14-
[targets]
15-
test = ["Markdown", "Serialization", "Test"]
9+
[compat]
10+
julia = "1.6"

README.md

Lines changed: 116 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,146 @@
1-
[![Build Status](https://travis-ci.org/andrewcooke/AutoHashEquals.jl.png)](https://travis-ci.org/andrewcooke/AutoHashEquals.jl)
2-
[![Coverage Status](https://coveralls.io/repos/andrewcooke/AutoHashEquals.jl/badge.svg)](https://coveralls.io/r/andrewcooke/AutoHashEquals.jl)
1+
[![Build Status](https://github.com/JuliaServices/AutoHashEquals.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaServices/AutoHashEquals.jl/actions/workflows/CI.yml?query=branch%3Amain)
2+
[![Coverage](https://codecov.io/gh/JuliaServices/AutoHashEquals.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaServices/AutoHashEquals.jl)
33

4-
# AutoHashEquals
4+
# AutoHashEquals.jl - Automatically define hash and equals for Julia.
55

6-
A macro to add == and hash() to composite types (ie struct and mutable struct
7-
blocks).
6+
A macro to add `==` and `hash()` to struct types: `@auto_hash_equals`.
87

9-
For example:
8+
# `@auto_hash_equals`
9+
10+
The macro `@auto_hash_equals` produces an implementation of `Base.hash(x)` that computes the hash code when invoked.
11+
12+
You use it like so:
1013

1114
```julia
12-
@auto_hash_equals mutable struct Foo
13-
a::Int
14-
b
15+
@auto_hash_equals struct Box{T}
16+
x::T
1517
end
1618
```
1719

18-
becomes
20+
which is translated to
1921

2022
```julia
21-
mutable struct Foo
22-
a::Int
23-
b
23+
struct Box{T}
24+
x::T
2425
end
25-
Base.hash(a::Foo, h::UInt) = hash(a.b, hash(a.a, hash(:Foo, h)))
26-
Base.(:(==))(a::Foo, b::Foo) = isequal(a.b, b.b) && isequal(a.a, b.a) && true
26+
Base.hash(x::Box, h::UInt) = hash(x.x, hash(:Box, h))
27+
Base.(:(==))(a::Box, b::Box) = isequal(a.x, b.x)
2728
```
2829

29-
Where
30+
We do not take the type arguments of a generic type into account for either `hash` or `==` unless `typearg=true` is specified (see below). So a `Box{Int}(1)` will test equal to a `Box{Any}(1)`.
31+
32+
## User-specified hash function
33+
34+
You can specify the hash function to be implemented, by naming it before the struct definition with a keyword argument `hashfn`:
35+
36+
```julia
37+
@auto_hash_equals hashfn=SomePackage.myhash struct Foo
38+
x
39+
y
40+
end
41+
```
42+
43+
In this case the macro implements both `SomePackage.myhash` and `Base.hash` for `Foo`.`
44+
45+
## Caching the hash value
46+
47+
You can have the hash value precomputed and stored in a hidden field, by adding the keyword argument `cache=true`. This 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.
48+
49+
```julia
50+
@auto_hash_equals cache=true struct Box{T}
51+
x::T
52+
end
53+
```
54+
55+
this translates to
56+
57+
```julia
58+
struct Box{T}
59+
x::T
60+
_cached_hash::UInt
61+
function Box{T}(x) where T
62+
new(x, Base.hash(x, Base.hash(:Box)))
63+
end
64+
end
65+
function Base.hash(x::Box, h::UInt)
66+
Base.hash(x._cached_hash, h)
67+
end
68+
function Base.hash(x::Box)
69+
x._cached_hash
70+
end
71+
function Base._show_default(io::IO, x::Box)
72+
AutoHashEqualsCached._show_default_auto_hash_equals_cached(io, x)
73+
end
74+
function Base.:(==)(a::Box, b::Box)
75+
a._cached_hash == b._cached_hash && Base.isequal(a.x, b.x)
76+
end
77+
function Box(x::T) where T
78+
Box{T}(x)
79+
end
80+
```
3081

31-
* we use `isequal()` because we want to match `Inf` values, etc.
82+
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.
3283

33-
* we include the type in the hash so that different types with the same
34-
contents don't collide
84+
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 `Box(1)` to get an object of type `Box{Int}`.
3585

36-
* the type and `true` make it simple to generate code for empty records
86+
## Specifying significant fields
3787

38-
* the `Base` module is explicitly used so that you don't need to
39-
import it
88+
You can specify which fields should be significant for the purposes of computing the hash function and checking equality:
4089

41-
## Background
90+
```julia
91+
@auto_hash_equals fields=(a,b) struct Foo
92+
a
93+
b
94+
c
95+
end
96+
```
4297

43-
Julia has two composite types: *value* types, defined with `struct`, and
44-
*record* types, defined with `mutable struct`.
98+
this translates to
4599

46-
Value types are intended for compact, immutable objects. They are stored on
47-
the stack, passed by value, and the default hash and equality are based on the
48-
literal bits in memory.
100+
```julia
101+
struct Foo
102+
a
103+
b
104+
c
105+
end
106+
function Base.hash(x::Foo, h::UInt)
107+
Base.hash(x.b, Base.hash(x.a, Base.hash(:Foo, h)))
108+
end
109+
function (Base).:(==)(a::Foo, b::Foo)
110+
Base.isequal(a.a, b.a) && Base.isequal(a.b, b.b)
111+
end
112+
```
49113

50-
Record types are allocated on the heap, are passed by reference, and the
51-
default hash and equality are based on the pointer value (the data address).
114+
## Specifying whether or not type arguments should be significant
52115

53-
When you embed a record type in a value type, then the pointer to the record
54-
type becomes part of the value type, and so is included in equality and hash.
116+
You can specify that type arguments should be significant for the purposes of computing the hash function and checking equality by adding the keyword parameter `typearg=true`. By default they are not significant. You can specify the default (they are not significant) with `typearg=false`:
55117

56-
Given the above, it is often necessary to define hash and equality for
57-
composite types. Particularly when record types are used (directly, or in a
58-
value type), and when records with the same contents are semantically equal.
118+
```julia-repl
119+
julia> @auto_hash_equals struct Box1{T}
120+
x::T
121+
end
122+
Box1
59123
60-
A common way to do this is to define the hash as a combination of the hashes
61-
of all the fields. Similarly, equality is often defined as equality of all
62-
fields.
124+
julia> Box1{Int}(1) == Box1{Any}(1)
125+
true
63126
64-
This macro automates this common approach.
127+
julia> hash(Box1{Int}(1))
128+
0x05014b35fc91d289
65129
66-
## Warnings
130+
julia> hash(Box1{Any}(1))
131+
0x05014b35fc91d289
67132
68-
If you use this macro for a mutable type, then the hash depends on the
69-
contents of that type, so changing the contents changes the hash. Such types
70-
should not be stored in a hash table (Dict) and then mutated, because the
71-
objects will be "lost" (as the hash table *assumes* that hash is constant).
133+
julia> @auto_hash_equals typearg=true struct Box2{T}
134+
x::T
135+
end
136+
Box2
72137
73-
More generally, **this macro is only useful for mutable types when they are
74-
used as *immutable* records**.
138+
julia> Box2{Int}(1) == Box2{Any}(1)
139+
false
75140
76-
## Credits
141+
julia> hash(Box2{Int}(1))
142+
0xb7650cb555d6aafa
77143
78-
Thanks to Michael Hatherly, Yichao Yu, and Carlo Lucibello.
144+
julia> hash(Box2{Any}(1))
145+
0xefe691a94f296c61
146+
```

REQUIRE

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)