218 lines
11 KiB
Markdown
218 lines
11 KiB
Markdown
# Designing an Idiomatic Interface
|
|
|
|
Currently, the contract for package was built without design.
|
|
More attention was paid to implementing the underlying functionality of the cuckoo hashing.
|
|
|
|
With the fundamentals of the algorithm built, our API contract should be revisited.
|
|
It should align closer to the following principles:
|
|
|
|
- **Similarity to the builtin map.**
|
|
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.
|
|
|
|
## Current State
|
|
|
|
### Interface of the Builtin Map
|
|
|
|
Listed below is every interface provided by Go to the built-in map object.
|
|
Also included, are the functions from the package `maps` in the standard library.
|
|
|
|
<details>
|
|
<summary>Interfaces</summary>
|
|
|
|
| # | Builtin Interface | Description |
|
|
| --- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| 1 | `m := make(map[K]V)` | Returns an empty map using the built-in `make()` function. |
|
|
| 2 | `m := make(map[K]V, hint)` | Returns an empty map using `make()`, with a capacity 'hint'. This hint is how many items the map expects to hold, _not_ a measure of how large it is. |
|
|
| 3 | `m := map[K]V{...}` | Returns a map, which may be filled with entries in the ellipsis (optional). |
|
|
| 4 | `var m map[K]V` | Defines an empty _variable_ that holds a map. This differs from #1 because `m` is uninitialized (nil) here. |
|
|
| 5 | `m[k] := v` | Assigns the value of `k` to `v`. |
|
|
| 6 | `v := m[k]` | Returns the value of `k` if it exists. Otherwise, `v` is uninitialized. |
|
|
| 7 | `v, ok := m[k]` | Similar to #6, except `ok` is equal to whether `v` is initialized. This is comma-ok notation. |
|
|
| 8 | `for k, v := range m` | Iterates over every key-value pair in `m`. The order is random. |
|
|
| 9 | `delete(m, k)` | Unassigns the value `k`. Returns no value. |
|
|
| 10 | `clear(m)` | Unassigns all keys in `m`. Returns no value. |
|
|
| 11 | `n := len(m)` | Returns the number of entries in `m`. If nil, `m` returns 0. |
|
|
| 12 | `m2 := maps.Clone(m)` | Returns a copy of `m`. |
|
|
| 13 | `maps.Copy(dst, src)` | Assigns every entry of `src` in `dst`. |
|
|
| 14 | `ok := maps.Equal(m1, m2)` | Returns true iff `m1` and `m2` the same entries. |
|
|
| 15 | `ok := maps.EqualFunc(m1, m2, fn)` | Like #14, but with a custom comparator for non-comparable values. |
|
|
| 16 | `maps.DeleteFunc(m, fn)` | Removes every entry in `m` which satisfies `fn`. Returns no value. |
|
|
| 17 | `it2 := maps.All(m)` | Returns an 2D iterator over every key-value pair. |
|
|
| 18 | `it := maps.Keys(m)` | Returns an iterator over every key. |
|
|
| 19 | `it := maps.Values(m)` | Returns an iterator over every value. There can be duplicates. |
|
|
| 20 | `m := maps.Collect(seq)` | Returns a map, with every entry defined in a 2D iterator over key-value pairs. |
|
|
| 21 | `maps.Insert(m, seq)` | Assigns to `m` all key-value pairs in 2D iterator `seq`. Returns no value. |
|
|
|
|
</details>
|
|
|
|
### Interface of `go-cuckoo`
|
|
|
|
On the other hand, here is the current contract for `go-cuckoo`.
|
|
|
|
<details>
|
|
<summary>Interfaces</summary>
|
|
|
|
| # | Builtin Interface | Description |
|
|
| --- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
| 1 | `m := New(opts...)` | Creates a table using the default hash and equal function. The options configure its behavior. Confined to comparable keys. |
|
|
| 2 | `m := NewBy(keyFunc, opts...)` | Like #1, but allows any key type. A `keyFunc` is used to derive a comparable key. |
|
|
| 3 | `m := NewCustom(hashA, hashB, equalFunc, opts...)` | Like #1, but allows control over the hashes used to allow any key type. An `equalFunc` determines key equality. |
|
|
| 4 | `seq := m.Entries()` | Returns an unordered 2D iterator of all key-value pairs in the table. |
|
|
| 5 | `v := m.Find(k)` | Removes the value for `k`. Returns true if `k` existed. |
|
|
| 6 | `v, ok := m.Get(k)` | Returns the value for `k` in the table. Also, returns true if the `k` exists, otherwise false. When false, `v` is undefined. |
|
|
| 7 | `ok := m.Has(k)` | Returns true if `k` is in the table. |
|
|
| 8 | `err := m.Put(k, v)` | Sets value `v` for key `k`. Otherwise, returns error. |
|
|
| 9 | `n := m.Size()` | Returns the number of items in `m`. |
|
|
| 10 | `str := m.String()` | Returns `m` as a string in the format "table[k1:v1 k2:v2 ...]". |
|
|
| 11 | `cap := m.TotalCapacity()` | Returns how many slots `m` has allocated. |
|
|
| 12 | `ok := m.Drop(k)` | Removes `k` from the table. Returns whether the key had existed. |
|
|
|
|
</details>
|
|
|
|
### Determining Similarity
|
|
|
|
So, how do the two relate?
|
|
Listed below is an analysis of every built-in interface.
|
|
Each is compared against what `go-cuckoo` offers, and if any changes seem necessary.
|
|
|
|
Specifically, here we are checking for functionality.
|
|
Is there functionality that this offers which `go-cuckoo` does not?
|
|
This check will check accessibility, but not discoverability.
|
|
The latter will be considered later.
|
|
|
|
<details>
|
|
<summary>✅ <code>m := make(map[K]V)</code></summary>
|
|
|
|
The analog is `m := New()`.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>❌ <code>m := make(map[K]V, hint)</code></summary>
|
|
|
|
This has no analog.
|
|
|
|
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.
|
|
|
|
- Capacity is an internal measure, used to optimize space/speed.
|
|
It is hidden from the user because it depends on the underlying implementation, which may change.
|
|
- Expected size requires the map must hold a number of items before resizing.
|
|
This is tangeable and agnostic to implementation, hence why it is given to the user.
|
|
|
|
In short, this interface defines expected size, but `Capacity()` defines capacity.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>❌ <code>m := map[K]V{...}</code></summary>
|
|
|
|
This has no simple analog.
|
|
The closest is:
|
|
|
|
```go
|
|
m := New[K, V]()
|
|
for k, v := range startingEntries {
|
|
m.Put(k, v)
|
|
}
|
|
```
|
|
|
|
While it is idiomatic, it is far less ergonomic.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>✅ <code>var m map[K]V</code></summary>
|
|
|
|
The analog is `var m Table[K, V]`.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>m[k] := v</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>v := m[k]</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>v, ok := m[k]</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>for k, v := range m</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>delete(m, k)</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>clear(m)</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>n := len(m)</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>m2 := maps.Clone(m)</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>maps.Copy(dst, src)</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>ok := maps.Equal(m1, m2)</code></summary>
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><code>ok := maps.EqualFunc(m1, m2, fn)</code></summary>
|
|
|
|
</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
|