Skip to content

Commit 5d3c880

Browse files
authored
Merge pull request JuliaCollections#96 from ExpandingMan/em/overhaul
major refactor (includes tons of documentation)
2 parents 3e101cc + 486305f commit 5d3c880

25 files changed

+1520
-1183
lines changed

.github/workflows/CI.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ jobs:
1313
fail-fast: false
1414
matrix:
1515
version:
16-
- '1.0'
1716
- '1.6'
1817
- '1'
1918
- 'nightly'

NEWS.md

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

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "AbstractTrees"
22
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
3-
version = "0.3.4"
3+
version = "0.3.5"
44
authors = ["Keno Fischer <[email protected]>"]
55

66
[deps]

docs/make.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ makedocs(
66
authors = "Keno Fischer",
77
format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"),
88
pages = [
9-
"index.md",
10-
"implementing.md",
11-
"api.md",
9+
"Home" => "index.md",
10+
"Iteration" => "iteration.md",
11+
"Internals" => "internals.md",
12+
"FAQ" => "faq.md",
1213
],
1314
)
1415

docs/src/api.md

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

docs/src/faq.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
```@meta
2+
CurrentModule = AbstractTrees
3+
```
4+
5+
# What are the breaking changes in 0.4?
6+
Most trees which only define methods for the single-argument version of `children` should not be
7+
affected by breaking changes in 0.4, though authors of packages containing these should review the
8+
new trait system to ensure they have added any appropriate traits which can improve performance.
9+
10+
Iterators types do *not* have breaking changes.
11+
12+
There are quite a few breaking changes for features of AbstractTrees.jl which were not documented or
13+
poorly documented and were therefore unlikely to be used.
14+
15+
The most significant changes are for indexed trees which now rely on the [`IndexNode`](@ref) object
16+
and a dedicated set of methods. Authors of packages using indexed trees should review [The Indexed
17+
Tree Interface](@ref). Roughly speaking, the changes are
18+
- `children(tree, node)` ``\rightarrow`` `childindices(tree, node_index)`
19+
- `Iterator(tree)` ``\rightarrow`` `Iterator(IndexNode(tree))`
20+
- Check if your tree satisfies the [`StoredParents`](@ref) or [`StoredSiblings`](@ref) traits.
21+
- Consider defining [`childrentype`](@ref) or [`childtype`](@ref) (can make some algorithms closer
22+
to type-stable).
23+
24+
25+
# Why were breaking changes necessary for 0.4?
26+
Prior to v0.4 AbstractTrees.jl confused the distinct concepts:
27+
- A tree node.
28+
- Values associated with the node (what is now obtained by [`nodevalue`](@ref)).
29+
- The position of a node in a tree.
30+
- A tree in its entirety.
31+
32+
This led to inconsistent implementations particularly of indexed tree types even within
33+
AbstractTrees.jl itself. As of 0.4 the package is much more firmly grounded in the concept of a
34+
*node*, alternative methods for defining trees are simply adaptors from objects to nodes, in
35+
particular [`IndexNode`](@ref).
36+
37+
A summary of major internal changes from 0.3 to 0.4 is as follows:
38+
- All indexed tree methods of basic tree functions have been removed. Indexed trees now have
39+
dedicated methods such as `childindices` and `parentindex`.
40+
- Nodes can now implement `nodevalue` which allows for distinction between values associated with
41+
the nodes and the nodes themselves.
42+
- All tree navigation is now based on "cursors". A cursor provides the necessary information for
43+
moving betweeen adjacent nodes of a tree. Iterators now specify the movement among cursor
44+
nodes.
45+
- Iterators now depend only on iterator states. This is mostly for internal convenience and does not
46+
change the external API.
47+
- `treemap` and `treemap!` have been replaced with versions that depend on [`MapNode`](@ref).
48+
49+
# Why aren't all iterators trees by default?
50+
Iteration is very widely implemented for Julia types and there are many types which define iteration
51+
but which don't make sense as trees. Major examples in `Base` alone include `Number` and `String`,
52+
`Char` and `Task`. If there are this many examples in `Base` there are likely to be a lot more in
53+
other packages.
54+
55+
# Why does `treemap` return a special node type?
56+
As described above, older versions of this package conflate tree nodes with values attached to them.
57+
This makes sense for certain built-in types, particularly arrays, but it imposes constraints on what
58+
types of nodes a tree can have. In particular, it requires all nodes to be container types (so that
59+
they can contain their children). It was previously not possible to have a tree in which, e.g. the
60+
integer `1` was anything other than a leaf node.
61+
62+
The function `treemap` is special in that it must choose an appropriate output type for an entire
63+
tree. Nodes of this output tree cannot be chosen to be a simple array, since the contents of arrays
64+
would be fully-determined by their children. In other words, a `treemap` based on arrays can only
65+
map leaves.
66+
67+
Introducing a new type becomes necessary to ensure that it can accommodate arbitrary output types.
68+
69+
# Why is my code type unstable?
70+
Guaranteeing type stability when iterating over trees is challenging to say the least. There are
71+
several major obstacles
72+
- The children of a tree node do not, in general, have the same type as their parent.
73+
- Even if it is easy to infer the type of a node's immediate children, it is usually much harder to
74+
infer the types of the node's more distant descendants.
75+
- Navigating a tree requires inferring not just the types of the children but the types of the
76+
children's *iteration states*. To make matters worse, Julia's `Base` does not include traits
77+
for describing these, and the `Base` iteration protocol makes very few assumptions about them.
78+
79+
All of this means that you are unlikely to get type-stable code from AbstractTrees.jl without some
80+
effort.
81+
82+
The simplest way around this is to define the `eltype` of tree iterators
83+
```julia
84+
Base.IteratorEltype(::Type{<:TreeIterator{ExampleNode}}) = Base.HasEltype()
85+
Base.eltype(::Type{<:TreeIterator{ExampleNode}}) = ExampleNode
86+
```
87+
which is equivalent to asserting that all nodes of a tree are of the same type. Performance
88+
critical code must ensure that it is possible to construct such a tree, which may not be trivial.
89+
90+
Note that even after defining `Base.eltype` it might still be difficult to achieve type-stability
91+
due to the aforementioned difficulties with iteration states. The most reliable around this is to
92+
ensure that the object returned by `children` is indexable and that the node has the
93+
`IndexedChildren` state. This guarantees that `Int` can always be used as an iteration state.

docs/src/implementing.md

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

0 commit comments

Comments
 (0)