|
| 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. |
0 commit comments