docs: finished congruency, started target state
This commit is contained in:
@@ -6,7 +6,10 @@ More attention was paid to implementing the underlying functionality of the cuck
|
|||||||
With the fundamentals of the algorithm built, our API contract should be revisited.
|
With the fundamentals of the algorithm built, our API contract should be revisited.
|
||||||
It should align closer to the following principles:
|
It should align closer to the following principles:
|
||||||
|
|
||||||
- **Similarity to the builtin map.**
|
- **Congruency to the builtin map.**
|
||||||
|
Our cuckoo table should have the same core functionality as Go's built-in map.
|
||||||
|
|
||||||
|
- **Familiarity to the builtin map.**
|
||||||
If our cuckoo table behaves similarly to Go's standard map, our user will intuitively know how to use it.
|
If our cuckoo table behaves similarly to Go's standard map, our user will intuitively know how to use it.
|
||||||
This lowers the cognitive load our developers must carry.
|
This lowers the cognitive load our developers must carry.
|
||||||
|
|
||||||
@@ -70,15 +73,19 @@ On the other hand, here is the current contract for `go-cuckoo`.
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Determining Similarity
|
### Determining Congruency
|
||||||
|
|
||||||
So, how do the two relate?
|
So, how does the core functionality compare?
|
||||||
Listed below is an analysis of every built-in interface.
|
Listed below is an analysis of every interface in Go's standard map.
|
||||||
Each is compared against what `go-cuckoo` offers, and if any changes seem necessary.
|
Each is compared against what `go-cuckoo` offers, and categorized into the following groups:
|
||||||
|
|
||||||
|
- ✅ Covered: an analog exists.
|
||||||
|
- ⚠️ Partial: workaround available.
|
||||||
|
- ❌ Gap: no analog yet; addressed in [Target State](#solving-congruency).
|
||||||
|
|
||||||
Specifically, here we are checking for functionality.
|
Specifically, here we are checking for functionality.
|
||||||
Is there functionality that this offers which `go-cuckoo` does not?
|
Is there functionality that this offers which `go-cuckoo` does not?
|
||||||
This check will check accessibility, but not discoverability.
|
We are checking accessibility, but not discoverability.
|
||||||
The latter will be considered later.
|
The latter will be considered later.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -89,9 +96,9 @@ The analog is `m := New()`.
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>❌ <code>m := make(map[K]V, hint)</code></summary>
|
<summary>⚠️ <code>m := make(map[K]V, hint)</code></summary>
|
||||||
|
|
||||||
This has no analog.
|
This has no simple analog.
|
||||||
|
|
||||||
It is close to `m := New(Capacity(hint))`, but it assigns starting capacity, not expected size.
|
It is close to `m := New(Capacity(hint))`, but it assigns starting capacity, not expected size.
|
||||||
For the built-in map, these are two separate things.
|
For the built-in map, these are two separate things.
|
||||||
@@ -129,88 +136,223 @@ The analog is `var m Table[K, V]`.
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>m[k] := v</code></summary>
|
<summary>✅ <code>m[k] := v</code></summary>
|
||||||
|
|
||||||
|
The analog is `err := m.Put(k, v)`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>v := m[k]</code></summary>
|
<summary>✅ <code>v := m[k]</code></summary>
|
||||||
|
|
||||||
|
The analog is `v := m.Find(k)`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>v, ok := m[k]</code></summary>
|
<summary>✅ <code>v, ok := m[k]</code></summary>
|
||||||
|
|
||||||
|
The analog is `v, ok := m.Get(k)`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>for k, v := range m</code></summary>
|
<summary>✅ <code>for k, v := range m</code></summary>
|
||||||
|
|
||||||
|
The analog is `for k, v := range m.Entries()`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>delete(m, k)</code></summary>
|
<summary>✅ <code>delete(m, k)</code></summary>
|
||||||
|
|
||||||
|
The analog is `ok := m.Drop(k)`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>clear(m)</code></summary>
|
<summary>❌ <code>clear(m)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
The easiest may to do this is to delete all items individually:
|
||||||
|
|
||||||
|
```go
|
||||||
|
for k := range m.Entries() {
|
||||||
|
m.Drop(k)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>n := len(m)</code></summary>
|
<summary>✅ <code>n := len(m)</code></summary>
|
||||||
|
|
||||||
|
The analog is `n := m.Size()`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>m2 := maps.Clone(m)</code></summary>
|
<summary>❌ <code>m2 := maps.Clone(m)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
The easiest way to do this currently is to make a new map, and manually add the items.
|
||||||
|
|
||||||
|
```go
|
||||||
|
m2 := cuckoo.Table[K, V]()
|
||||||
|
|
||||||
|
for k, v := range m.Entries() {
|
||||||
|
m2.Put(k, v)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This gets complicated by the various options available to the user.
|
||||||
|
Furthermore, any custom `EqualFunc`, `keyFunc` or `Hash` is not transferred.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>maps.Copy(dst, src)</code></summary>
|
<summary>❌ <code>maps.Copy(dst, src)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
The simplest way to do this is with a for-loop.
|
||||||
|
|
||||||
|
```go
|
||||||
|
for k, v := range src.Entries() {
|
||||||
|
dst.Put(k, v)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>❌ <code>ok := maps.Equal(m1, m2)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
Users have to manually check the key-value pairs to determine equality.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>❌ <code>ok := maps.EqualFunc(m1, m2, fn)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
Users have to manually check the key-value pairs to determine equality.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>❌ <code>maps.DeleteFunc(m, fn)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
Users have to manually delete keys.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅ <code>it2 := maps.All(m)</code></summary>
|
||||||
|
|
||||||
|
The analog is `it2 := m.Entries()`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>⚠️ <code>it := maps.Keys(m)</code></summary>
|
||||||
|
|
||||||
|
There is no simple analog.
|
||||||
|
|
||||||
|
A close neighbor is `it2 := m.Entries()`.
|
||||||
|
Users can use this in a for-loop, and pick out just the keys:
|
||||||
|
|
||||||
|
```go
|
||||||
|
for k := range m.Entries() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>⚠️ <code>it := maps.Values(m)</code></summary>
|
||||||
|
|
||||||
|
There is no simple analog.
|
||||||
|
|
||||||
|
A close neighbor is `it2 := m.Entries()`.
|
||||||
|
Users can use this in a for-loop, and pick out just the values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
for _, v := range m.Entries() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>❌ <code>m := maps.Collect(seq)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>❌ <code>maps.Insert(m, seq)</code></summary>
|
||||||
|
|
||||||
|
There is no analog.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Target State
|
||||||
|
|
||||||
|
### Solving Congruency
|
||||||
|
|
||||||
|
The following changes will be made to accomodate for congruency:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><code>ok := maps.EqualFunc(m1, m2, fn)</code></summary>
|
||||||
|
|
||||||
|
To solve this, we need a new function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func EqualFunc[K, V1, V2 any](t1 *Table[K, V1], t2 *Table[K, V2], eq func(V1, V2) bool) bool
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is free, and not bound as a receiver function.
|
||||||
|
(It is called `cuckoo.Equal(t1, t2)`, not `t1.Equals(t2)`.)
|
||||||
|
The latter implies `t1` has authority, when in fact neither do.
|
||||||
|
|
||||||
|
Equality will be defined as:
|
||||||
|
|
||||||
|
1. Neither table has a key the other doesn't.
|
||||||
|
2. Each key has the same value in each table.
|
||||||
|
Parameter `eq` determines this equality.
|
||||||
|
|
||||||
|
Custom `EqualFunc`'s complicate this, as they modulate key identity in tables.
|
||||||
|
If two tables may differ on whether two keys are different, this function might break.
|
||||||
|
So, we must assume that:
|
||||||
|
|
||||||
|
- Both tables have `EqualFunc`'s which 'agree' on the identity of the keys present in the tables.
|
||||||
|
Agreement is defined as: if two keys are distinct in one table, they are distinct in the other.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><code>ok := maps.Equal(m1, m2)</code></summary>
|
<summary><code>ok := maps.Equal(m1, m2)</code></summary>
|
||||||
|
|
||||||
</details>
|
The addition of `cuckoo.EqualFunc` makes an implementation trivial:
|
||||||
|
|
||||||
<details>
|
```go
|
||||||
<summary><code>ok := maps.EqualFunc(m1, m2, fn)</code></summary>
|
func Equal[K any, V comparable](t1, t2 *Table[K, V]) bool {
|
||||||
|
return EqualFunc(t1, t2, DefaultEqualFunc[V])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To conform with the standard library, a new function should be added.
|
||||||
|
Once again, the function is free because it is symmetric.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>maps.DeleteFunc(m, fn)</code></summary>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>it2 := maps.All(m)</code></summary>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>it := maps.Keys(m)</code></summary>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>it := maps.Values(m)</code></summary>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>m := maps.Collect(seq)</code></summary>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code>maps.Insert(m, seq)</code></summary>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Target State
|
|
||||||
|
|||||||
Reference in New Issue
Block a user