-
-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate walks out from fmap
and add #39 to fcollect
#43
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e051e21
Separate walks out from fmap
darsnack 3f11d5a
Add AnonymousWalk and update docstrings
darsnack 27a8f8c
Add depwarn for AnonymousWalk
darsnack cb9a0f2
Add AnonymousWalk docstring to docs
darsnack 31cb97a
Rebase with #39
darsnack e402dad
Apply suggestions from code review
darsnack ffd6ae9
Add new caching behavior to fcollect
darsnack File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
fmap(walk::AbstractWalk, f, x, ys...) = walk((xs...) -> fmap(walk, f, xs...), x, ys...) | ||
|
||
function fmap(f, x, ys...; exclude = isleaf, | ||
walk = DefaultWalk(), | ||
cache = IdDict(), | ||
prune = NoKeyword()) | ||
_walk = ExcludeWalk(AnonymousWalk(walk), f, exclude) | ||
if !isnothing(cache) | ||
_walk = CachedWalk(_walk, prune, cache) | ||
end | ||
fmap(_walk, f, x, ys...) | ||
end | ||
|
||
fmapstructure(f, x; kwargs...) = fmap(f, x; walk = StructuralWalk(), kwargs...) | ||
|
||
fcollect(x; exclude = v -> false) = | ||
fmap(ExcludeWalk(CollectWalk(), _ -> nothing, exclude), _ -> nothing, x) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
""" | ||
AbstractWalk | ||
|
||
Any walk for use with [`fmap`](@ref) should inherit from this type. | ||
A walk subtyping `AbstractWalk` must satisfy the walk function interface: | ||
```julia | ||
struct MyWalk <: AbstractWalk end | ||
|
||
function (::MyWalk)(recurse, x, ys...) | ||
# implement this | ||
end | ||
``` | ||
The walk function is called on a node `x` in a Functors tree. | ||
It may also be passed associated nodes `ys...` in other Functors trees. | ||
The walk function recurses further into `(x, ys...)` by calling | ||
`recurse` on the child nodes. | ||
The choice of which nodes to recurse and in what order is custom to the walk. | ||
""" | ||
abstract type AbstractWalk end | ||
|
||
""" | ||
AnonymousWalk(walk_fn) | ||
|
||
Wrap a `walk_fn` so that `AnonymousWalk(walk_fn) isa AbstractWalk`. | ||
This type only exists for backwards compatability and should be directly used. | ||
Attempting to wrap an existing `AbstractWalk` is a no-op (i.e. it is not wrapped). | ||
""" | ||
struct AnonymousWalk{F} <: AbstractWalk | ||
walk::F | ||
|
||
function AnonymousWalk(walk::F) where F | ||
Base.depwarn("Wrapping a custom walk function as an `AnonymousWalk`. Future versions will only support custom walks that explicitly subtyle `AbstractWalk`.", :AnonymousWalk) | ||
return new{F}(walk) | ||
end | ||
end | ||
# do not wrap an AbstractWalk | ||
AnonymousWalk(walk::AbstractWalk) = walk | ||
|
||
(walk::AnonymousWalk)(recurse, x, ys...) = walk.walk(recurse, x, ys...) | ||
|
||
""" | ||
DefaultWalk() | ||
|
||
The default walk behavior for Functors.jl. | ||
Walks all the [`Functors.children`](@ref) of trees `(x, ys...)` based on | ||
the structure of `x`. | ||
The resulting mapped child nodes are restructured into the type of `x`. | ||
|
||
See [`fmap`](@ref) for more information. | ||
""" | ||
struct DefaultWalk <: AbstractWalk end | ||
|
||
function (::DefaultWalk)(recurse, x, ys...) | ||
func, re = functor(x) | ||
yfuncs = map(y -> functor(typeof(x), y)[1], ys) | ||
re(map(recurse, func, yfuncs...)) | ||
end | ||
|
||
""" | ||
StructuralWalk() | ||
|
||
A structural variant of [`Functors.DefaultWalk`](@ref). | ||
The recursion behavior is identical, but the mapped children are not restructured. | ||
|
||
See [`fmapstructure`](@ref) for more information. | ||
""" | ||
struct StructuralWalk <: AbstractWalk end | ||
|
||
(::StructuralWalk)(recurse, x) = map(recurse, children(x)) | ||
|
||
""" | ||
ExcludeWalk(walk, fn, exclude) | ||
|
||
A walk that recurses nodes `(x, ys...)` according to `walk`, | ||
except when `exclude(x)` is true. | ||
Then, `fn(x, ys...)` is applied instead of recursing further. | ||
|
||
Typically wraps an existing `walk` for use with [`fmap`](@ref). | ||
""" | ||
struct ExcludeWalk{T, F, G} <: AbstractWalk | ||
walk::T | ||
fn::F | ||
exclude::G | ||
end | ||
|
||
(walk::ExcludeWalk)(recurse, x, ys...) = | ||
walk.exclude(x) ? walk.fn(x, ys...) : walk.walk(recurse, x, ys...) | ||
|
||
struct NoKeyword end | ||
|
||
usecache(::Union{AbstractDict, AbstractSet}, x) = | ||
isleaf(x) ? anymutable(x) : ismutable(x) | ||
usecache(::Nothing, x) = false | ||
|
||
@generated function anymutable(x::T) where {T} | ||
ismutabletype(T) && return true | ||
subs = [:(anymutable(getfield(x, $f))) for f in QuoteNode.(fieldnames(T))] | ||
return Expr(:(||), subs...) | ||
end | ||
|
||
""" | ||
CachedWalk(walk[; prune]) | ||
|
||
A walk that recurses nodes `(x, ys...)` according to `walk` and storing the | ||
output of the recursion in a cache indexed by `x` (based on object ID). | ||
Whenever the cache already contains `x`, either: | ||
- `prune` is specified, then it is returned, or | ||
- `prune` is unspecified, and the previously cached recursion of `(x, ys...)` | ||
returned. | ||
|
||
Typically wraps an existing `walk` for use with [`fmap`](@ref). | ||
""" | ||
struct CachedWalk{T, S} <: AbstractWalk | ||
walk::T | ||
prune::S | ||
cache::IdDict{Any, Any} | ||
end | ||
CachedWalk(walk; prune = NoKeyword(), cache = IdDict()) = | ||
CachedWalk(walk, prune, cache) | ||
|
||
function (walk::CachedWalk)(recurse, x, ys...) | ||
should_cache = usecache(walk.cache, x) | ||
if should_cache && haskey(walk.cache, x) | ||
return walk.prune isa NoKeyword ? walk.cache[x] : walk.prune | ||
else | ||
ret = walk.walk(recurse, x, ys...) | ||
if should_cache | ||
walk.cache[x] = ret | ||
end | ||
return ret | ||
end | ||
end | ||
|
||
""" | ||
CollectWalk() | ||
|
||
A walk that recurses into a node `x` via [`Functors.children`](@ref), | ||
storing the recursion history in a cache. | ||
The resulting ordered recursion history is returned. | ||
|
||
See [`fcollect`](@ref) for more information. | ||
""" | ||
struct CollectWalk <: AbstractWalk | ||
cache::Base.IdSet{Any} | ||
output::Vector{Any} | ||
end | ||
CollectWalk() = CollectWalk(Base.IdSet(), Any[]) | ||
|
||
# note: we don't have an `OrderedIdSet`, so we use an `IdSet` for the cache | ||
# (to ensure we get exactly 1 copy of each distinct array), and a usual `Vector` | ||
# for the results, to preserve traversal order (important downstream!). | ||
function (walk::CollectWalk)(recurse, x) | ||
if usecache(walk.cache, x) && (x in walk.cache) | ||
return walk.output | ||
end | ||
# to exclude, we wrap this walk in ExcludeWalk | ||
usecache(walk.cache, x) && push!(walk.cache, x) | ||
push!(walk.output, x) | ||
map(recurse, children(x)) | ||
|
||
return walk.output | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
using Functors, Test | ||
using Zygote | ||
using LinearAlgebra | ||
using StaticArrays | ||
|
||
@testset "Functors.jl" begin | ||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason not to constrain these type parameters, at least for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason this type exists is to wrap
F
that isn't aAbstractWalk
but still satisfies theAbstractWalk
interface.