Skip to content

Commit 37d30bc

Browse files
committed
docs: work on upgrade guide
1 parent f1e92b9 commit 37d30bc

File tree

2 files changed

+131
-38
lines changed

2 files changed

+131
-38
lines changed

CHANGELOG.md

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

upgrade_guide_2.0.0.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# DynamicExpressions.jl v2.0 Upgrade Guide
2+
3+
DynamicExpressions.jl v2.0 introduces support for n-arity operators (nodes with arbitrary numbers of children),
4+
which required some breaking changes to implement. This guide will help you migrate your code from v1.x to v2.0.
5+
6+
## Breaking Changes Summary
7+
8+
- Types
9+
- `Node{T}` is now `Node{T,D}` where `D` is the maximum degree
10+
- `AbstractExpressionNode{T}` is now `AbstractExpressionNode{T,D}`
11+
- `AbstractNode` is now `AbstractNode{D}`
12+
- Accessors
13+
- You can now access children by index with `get_child(tree, i)`
14+
- `tree.l` should now be written as `get_child(tree, 1)`
15+
- `tree.r` should now be written as `get_child(tree, 2)`
16+
- _note: you can access multiple children with `get_children(tree, Val(degree))`_
17+
- You can now set children by index with `set_child!(tree, child, i)`
18+
- `tree.l = child` should now be written as `set_child!(tree, child, 1)`
19+
- `tree.r = child` should now be written as `set_child!(tree, child, 2)`
20+
- _note: you can set multiple children with `set_children!(tree, children)`_
21+
- Constructors
22+
- `Node{T}(; op=1, l=x)` should now be written as `Node{T}(; op=1, children=(x,))`
23+
- `Node{T}(; op=1, l=x, r=y)` should now be written as `Node{T}(; op=1, children=(x, y))`
24+
- You may now use `Node{T,D}(; op=1, children=(x,))` to specify degree other than the default of 2.
25+
- Types
26+
- Before, `Node{T}` had fields `l::Node{T}` and `r::Node{T}`.
27+
- Now, the type is `Node{T,D}`, and it has the field `children::NTuple{D,Nullable{Node{T,D}}}`.
28+
29+
## Necessary Changes to Your Code
30+
31+
The main breaking change that requires some modifications is patterns that
32+
explicitly match `tree.degree` in conditional logic. The `tree.degree == 0`
33+
branch can be left alone, but higher arity nodes should be generalized.
34+
35+
For code like this:
36+
37+
```julia
38+
# This pattern ONLY works for binary trees (degree ≤ 2)
39+
if tree.degree == 0
40+
# leaf node
41+
elseif tree.degree == 1
42+
# unary operator
43+
else # tree.degree == 2 # <-- This violates the assumption in 2.0
44+
# binary operator
45+
end
46+
```
47+
48+
You have two options for upgrading
49+
50+
1. Constrain your type signatures: Use `::AbstractExpressionNode{T,2}` to only accept binary trees, and refuse higher-arity nodes
51+
52+
```julia
53+
function my_function(tree::AbstractExpressionNode{T,2}) where T
54+
if tree.degree == 0
55+
# leaf
56+
elseif tree.degree == 1
57+
# unary
58+
else # tree.degree == 2, guaranteed
59+
# binary
60+
end
61+
end
62+
```
63+
64+
2. Rewrite your code to be more generic. (_Note that for recursive algorithms, you can often do things with a `tree_mapreduce`, which already handles the general case._)
65+
66+
```julia
67+
# 2: Handle arbitrary arity
68+
function my_function(tree::AbstractExpressionNode{T}) where T
69+
if tree.degree == 0
70+
# leaf
71+
else # higher arity
72+
deg = tree.degree
73+
for i in 1:deg
74+
child = get_child(tree, i)
75+
# process child...
76+
end
77+
end
78+
end
79+
```
80+
81+
However, normally what is done internally for max efficiency for the general approach is to use patterns like:
82+
83+
```julia
84+
@generated function my_function(tree::AbstractExpressionNode{T,D}) where {T,D}
85+
quote
86+
deg = tree.degree
87+
deg == 0 && process_leaf(tree)
88+
Base.Cartesian.@nif(
89+
$deg,
90+
i -> i == deg,
91+
i -> let children = get_children(tree, Val(i))
92+
# Now, `children` is a type-stable tuple of children
93+
end,
94+
)
95+
end
96+
end
97+
```
98+
99+
Note that the `@generated` is needed to pass `D` to the Cartesian macro.
100+
101+
## Property Access (Non-Breaking)
102+
103+
Note: `.l` and `.r` property access still work and will continue to be supported on types with `D == 2`. However, the generic accessors are more flexible, so upgrading to them is recommended.
104+
105+
```julia
106+
# old_child = tree.l
107+
old_child = get_child(tree, 1)
108+
109+
# tree.r = new_child
110+
set_child!(tree, new_child, 2)
111+
```
112+
113+
This lets you write code that prescribes arbitrary arity.
114+
115+
## Node Construction (Non-Breaking)
116+
117+
For binary trees, you can still use the syntax:
118+
119+
```julia
120+
x = Node{Float64}(; feature=1)
121+
tree = Node{Float64}(; op=1, children=(x,))
122+
```
123+
124+
For higher-arity trees, you may pass `D` to specify the maximum degree in the tree:
125+
126+
```julia
127+
x = Node{Float64,3}(; feature=1)
128+
y = Node{Float64,3}(; feature=2)
129+
z = Node{Float64,3}(; feature=3)
130+
tree = Node{Float64,3}(; op=1, children=(x, y, z))
131+
```

0 commit comments

Comments
 (0)