You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Functors.jl provides tools to express a powerful design pattern for dealing with large/ nested structures, as in machine learning and optimisation. For large machine learning models it can be cumbersome or inefficient to work with parameters as one big, flat vector, and structs help manage complexity; but it is also desirable to easily operate over all parameters at once, e.g. for changing precision or applying an optimiser update step.
16
+
Functors.jl provides tools to express a powerful design pattern for dealing with large / nested structures, as in machine learning and optimisation. For large machine learning models it can be cumbersome or inefficient to work with parameters as one big, flat vector, and structs help manage complexity; but it is also desirable to easily operate over all parameters at once, e.g. for changing precision or applying an optimiser update step.
17
+
18
+
## Basic Usage
17
19
18
20
Functors.jl provides `fmap` to make those things easy, acting as a 'map over parameters':
19
21
@@ -25,8 +27,6 @@ julia> struct Foo
25
27
y
26
28
end
27
29
28
-
julia>@functor Foo
29
-
30
30
julia> model =Foo(1, [1, 2, 3])
31
31
Foo(1, [1, 2, 3])
32
32
@@ -41,26 +41,32 @@ julia> struct Bar
41
41
x
42
42
end
43
43
44
-
julia>@functor Bar
45
-
46
44
julia> model =Bar(Foo(1, [1, 2, 3]))
47
45
Bar(Foo(1, [1, 2, 3]))
48
46
49
47
julia>fmap(float, model)
50
48
Bar(Foo(1.0, [1.0, 2.0, 3.0]))
51
49
```
52
50
51
+
> [!NOTE]
52
+
> Up to to v0.4, Functors.jl's functionality had to be opted in on custom types via the `@functor Foo` macro call.
53
+
> With v0.5 instead, this is no longer necessary: by default any type is recursively traversed up to the leaves
54
+
> and `ConstructionBase.constructorof` is used to reconstruct it.
55
+
> In order to opt-out of this behaviour and make a type non traversable you can use `@leaf Foo`.
56
+
57
+
## Further Details
58
+
53
59
The workhorse of `fmap` is actually a lower level function, `functor`:
((x =1, y = [1, 2, 3]), Functors.var"#3#6"{DataType}(Foo))
58
64
59
-
julia>re(map(float, xs))
65
+
julia>reconstruct(map(float, children))
60
66
Foo(1.0, [1.0, 2.0, 3.0])
61
67
```
62
68
63
-
`functor` returns the parts of the object that can be inspected, as well as a `re` function that takes those values and restructures them back into an object of the original type.
69
+
`functor` returns the parts of the object that can be inspected, as well as a `reconstruct` function that takes those values and restructures them back into an object of the original type.
64
70
65
71
To include only certain fields, pass a tuple of field names to `@functor`:
Copy file name to clipboardExpand all lines: docs/src/index.md
+29-11
Original file line number
Diff line number
Diff line change
@@ -4,13 +4,15 @@ Functors.jl provides a set of tools to represent [functors](https://en.wikipedia
4
4
5
5
The most straightforward use is to traverse a complicated nested structure as a tree, and apply a function `f` to every field it encounters along the way.
6
6
7
-
For large models it can be cumbersome or inefficient to work with parameters as one big, flat vector, and structs help manage complexity; but it may be desirable to easily operate over all parameters at once, e.g. for changing precision or applying an optimiser update step.
7
+
For large machine learning models it can be cumbersome or inefficient to work with parameters as one big, flat vector, and structs help manage complexity; but it may be desirable to easily operate over all parameters at once, e.g. for changing precision or applying an optimiser update step.
8
8
9
9
## Basic Usage and Implementation
10
10
11
-
When one marks a structure as [`@functor`](@ref) it means that Functors.jl is allowed to look into the fields of the instances of the struct and modify them. This is achieved through [`Functors.fmap`](@ref).
11
+
By default, julia types are marked as [`@functor`](@ref Functors.functor)s, meaning that Functors.jl is allowed to look into the fields of the instances of the struct and modify them. This is achieved through [`fmap`](@ref). To opt-out of this behaviour, use [`@leaf`](@ref) on your custom type.
12
12
13
-
The workhorse of fmap is actually a lower level function, functor:
13
+
```julia-repl
14
+
15
+
The workhorse of `fmap` is actually a lower level function, [`functor`](@ref Functors.functor):
14
16
15
17
```julia-repl
16
18
julia> using Functors
@@ -20,8 +22,6 @@ julia> struct Foo
20
22
y
21
23
end
22
24
23
-
julia> @functor Foo
24
-
25
25
julia> foo = Foo(1, [1, 2, 3]) # notice all the elements are integers
26
26
27
27
julia> xs, re = Functors.functor(foo)
@@ -50,13 +50,31 @@ julia> fmap(float, model)
50
50
Baz(1.0, 2)
51
51
```
52
52
53
-
Any field not in the list will be passed through as-is during reconstruction. This is done by invoking the default constructor, so structs that define custom inner constructors are expected to provide one that acts like the default.
53
+
Any field not in the list will be passed through as-is during reconstruction. This is done by invoking the default constructor accepting all fields as arguments, so structs that define custom inner constructors are expected to provide one that acts like the default.
54
+
55
+
The use of `@functor` with no fields argument as in `@functor Baz` is equivalent to `@functor Baz fieldnames(Baz)` and also equivalent to avoiding `@functor` altogether.
56
+
57
+
Using [`@leaf`](@ref) instead of [`@functor`](@ref) will prevent the fields of a struct from being traversed.
58
+
59
+
!!! warning "Change to opt-out behaviour in v0.5"
60
+
Previous releases of functors, up to v0.4, used an opt-in behaviour where structs were leaves functors unless marked with `@functor`. This was changed in v0.5 to an opt-out behaviour where structs are functors unless marked with `@leaf`.
61
+
62
+
## Which types are leaves?
63
+
64
+
By default all composite types in are functors and can be traversed, unless marked with [`@leaf`](@ref).
65
+
66
+
The following types instead are explicitly marked as leaves in Functors.jl:
67
+
-`Number`.
68
+
-`AbstractArray{<:Number}`, except for the wrappers `Transpose`, `Adjoint`, and `PermutedDimsArray`.
69
+
-`AbstractString`.
54
70
55
-
## Appropriate Use
71
+
This is because in typical application the internals of these are abstracted away and it is not desirable to traverse them.
56
72
57
-
!!! warning "Not everything should be a functor!"
58
-
Due to its generic nature it is very attractive to mark several structures as [`@functor`](@ref) when it may not be quite safe to do so.
73
+
## What if I get an error?
59
74
60
-
Typically, since any function `f` is applied to the leaves of the tree, but it is possible for some functions to require dispatching on the specific type of the fields causing some methods to be missed entirely.
75
+
Since by default Functors.jl tries to traverse most types e.g. when using [`fmap`](@ref), it is possible it fails in case the type has not an appropriate constructor. If use experience this issue, you have a few alternatives:
76
+
- Mark the type as a leaf using [`@leaf`](@ref)
77
+
- Use the `@functor` macro to specify which fields to traverse.
78
+
- Define an appropriate constructor for the type.
61
79
62
-
Examples of this include element types of arrays which typically have their own mathematical operations defined. Adding a [`@functor`](@ref)to such a type would end up missing methods such as `+(::MyElementType, ::MyElementType)`. Think `RGB` from Colors.jl.
80
+
If you are not able to traverse types in julia Base, please open an issue.
0 commit comments