Skip to content

Commit

Permalink
Make iterator interface docs clearer (JuliaLang#50994)
Browse files Browse the repository at this point in the history
A few beginner and intermediate Julia programmers shown both versions
thought this one was clearer.

Follows on from JuliaLang#50069

Two other minor changes:

- includes `Base.isdone` in documenter output so that it can be linked
from the table (this probably should have been done when we originally
included it in the table).
- update the warning on `isempty` about stateful iterators to be a
little less strident, because `isempty` shouldn't cause any bugs for
stateful iterators that correctly implement the documented interface
(and I think I fixed all the stateful iterators in Base quite a while
ago now).

Before:


![image](https://user-images.githubusercontent.com/6000761/243437416-65a0e54e-0dcd-4c79-88e2-087166766eb7.png)

After:


![image](https://github.com/JuliaLang/julia/assets/6000761/fab836ee-4e24-4a3e-a5ca-6ba582e4ccab)

I don't think it matters much, but the lengths of the columns are mostly
determined by the longest `code` span within them, because the code
spans are not wrapped. We can allow the browser to perform word wrapping
within the spans by including `<wbr />` elements or unicode
zero-width-space characters, but I can't get the cross references
(links) to work. This is what I tried:

```md
before

[`Base.IteratorEltype(IterType)`](@ref)

after (encouraging browsers to break the line at the dot):

[<code>Base.<wbr />IteratorEltype(IterType)</code>](@ref Base.IteratorEltype)

Which gives the following error:

┌ Error: reference for 'Base.IteratorEltype' could not be found in src/manual/interfaces.md.
└ @ Documenter.CrossReferences ~/src/julia/doc/deps/packages/Documenter/yf96B/src/Utilities/Utilities.jl:32
```
  • Loading branch information
cmcaine committed Aug 22, 2023
1 parent a4309ca commit 3c18223
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 50 deletions.
29 changes: 17 additions & 12 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -938,9 +938,10 @@ Determine whether a collection is empty (has no elements).
!!! warning
`isempty(itr)` may consume the next element of a stateful iterator `itr`
unless an appropriate `Base.isdone(itr)` or `isempty` method is defined.
Use of `isempty` should therefore be avoided when writing generic
code which should support any iterator type.
unless an appropriate [`Base.isdone(itr)`](@ref) method is defined.
Stateful iterators *should* implement `isdone`, but you may want to avoid
using `isempty` when writing generic code which should support any iterator
type.
# Examples
```jldoctest
Expand Down Expand Up @@ -1049,17 +1050,21 @@ end

# Iteration
"""
isdone(itr, state...) -> Union{Bool, Missing}
isdone(itr, [state]) -> Union{Bool, Missing}
This function provides a fast-path hint for iterator completion.
This is useful for mutable iterators that want to avoid having elements
consumed, if they are not going to be exposed to the user (e.g. to check
for done-ness in `isempty` or `zip`). Mutable iterators that want to
opt into this feature should define an isdone method that returns
true/false depending on whether the iterator is done or not. Stateless
iterators need not implement this function. If the result is `missing`,
callers may go ahead and compute `iterate(x, state...) === nothing` to
compute a definite answer.
This is useful for stateful iterators that want to avoid having elements
consumed if they are not going to be exposed to the user (e.g. when checking
for done-ness in `isempty` or `zip`).
Stateful iterators that want to opt into this feature should define an `isdone`
method that returns true/false depending on whether the iterator is done or
not. Stateless iterators need not implement this function.
If the result is `missing`, callers may go ahead and compute
`iterate(x, state) === nothing` to compute a definite answer.
See also [`iterate`](@ref), [`isempty`](@ref)
"""
isdone(itr, state...) = missing

Expand Down
1 change: 1 addition & 0 deletions doc/src/base/collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Base.LinRange

```@docs
Base.isempty
Base.isdone
Base.empty!
Base.length
Base.checked_length
Expand Down
56 changes: 18 additions & 38 deletions doc/src/manual/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,24 @@ to generically build upon those behaviors.

## [Iteration](@id man-interface-iteration)

### Required methods

| Method | Brief description |
|:---------------------- |:---------------------------------------------------------------------------------------- |
| `iterate(iter)` | Returns either a tuple of the first item and initial state or [`nothing`](@ref) if empty |
| `iterate(iter, state)` | Returns either a tuple of the next item and next state or `nothing` if no items remain |

Depending on the definition of `Base.IteratorSize(IterType)`, you may need to define additional methods:

| Value returned by `Base.IteratorSize(IterType)` | Required Methods |
|:----------------------------------------------- |:---------------------------------------------- |
| `Base.HasLength()` | [`length(iter)`](@ref) |
| `Base.HasShape{N}()` | `length(iter)` and [`size(iter, [dim])`](@ref) |
| `Base.IsInfinite()` | (*none*) |
| `Base.SizeUnknown()` | (*none*) |

Because the default definition of `Base.IteratorSize(IterType)` is `Base.HasLength()`, you will need to define at least one of `Base.IteratorSize(IterType)` and `length(iter)`.

| Method | Default definition | Brief description |
|:---------------------------- |:------------------ |:------------------------------------------------------------------------------------------------------------ |
| `Base.IteratorSize(IterType)`| `Base.HasLength()` | One of `Base.HasLength()`, `Base.HasShape{N}()`, `Base.IsInfinite()`, or `Base.SizeUnknown()` as appropriate |
| `length(iter)` | (*undefined*) | The number of items, if known |
| `size(iter, [dim])` | (*undefined*) | The number of items in each dimension, if known |

### Optional methods

| Method | Default definition | Brief description |
|:------------------------------- |:------------------ |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Base.IteratorEltype(IterType)` | `Base.HasEltype()` | Either `Base.EltypeUnknown()` or `Base.HasEltype()` as appropriate |
| `eltype(IterType)` | `Any` | The type of the first entry of the tuple returned by `iterate()` |
| `Base.isdone(iter[, state])` | `missing` | Fast-path hint for iterator completion. Should be defined for stateful iterators, or else `isempty(iter)` may call `iterate(iter[, state])` and mutate the iterator. |

| Value returned by `Base.IteratorEltype(IterType)` | Required Methods |
|:------------------------------------------------- |:------------------ |
| `Base.HasEltype()` | `eltype(IterType)` |
| `Base.EltypeUnknown()` | (*none*) |

### Description
There are two methods that are always required:

| Required method | Brief description |
|:----------------------- |:---------------------------------------------------------------------------------------- |
| [`iterate(iter)`](@ref) | Returns either a tuple of the first item and initial state or [`nothing`](@ref) if empty |
| `iterate(iter, state)` | Returns either a tuple of the next item and next state or `nothing` if no items remain |

There are several more methods that should be defined in some circumstances.
Please note that you should always define at least one of `Base.IteratorSize(IterType)` and `length(iter)` because the default definition of `Base.IteratorSize(IterType)` is `Base.HasLength()`.

| Method | When should this method be defined? | Default definition | Brief description |
|:--- |:--- |:--- |:--- |
| [`Base.IteratorSize(IterType)`](@ref) | If default is not appropriate | `Base.HasLength()` | One of `Base.HasLength()`, `Base.HasShape{N}()`, `Base.IsInfinite()`, or `Base.SizeUnknown()` as appropriate |
| [`length(iter)`](@ref) | If `Base.IteratorSize()` returns `Base.HasLength()` or `Base.HasShape{N}()` | (*undefined*) | The number of items, if known |
| [`size(iter, [dim])`](@ref) | If `Base.IteratorSize()` returns `Base.HasShape{N}()` | (*undefined*) | The number of items in each dimension, if known |
| [`Base.IteratorEltype(IterType)`](@ref) | If default is not appropriate | `Base.HasEltype()` | Either `Base.EltypeUnknown()` or `Base.HasEltype()` as appropriate |
| [`eltype(IterType)`](@ref) | If default is not appropriate | `Any` | The type of the first entry of the tuple returned by `iterate()` |
| [`Base.isdone(iter, [state])`](@ref) | **Must** be defined if iterator is stateful | `missing` | Fast-path hint for iterator completion. If not defined for a stateful iterator then functions that check for done-ness, like `isempty()` and `zip()`, may mutate the iterator and cause buggy behaviour! |

Sequential iteration is implemented by the [`iterate`](@ref) function. Instead
of mutating objects as they are iterated over, Julia iterators may keep track
Expand Down

0 comments on commit 3c18223

Please sign in to comment.