From 41b6b7617df8e44d653ed6bff23bf9e2d6066b48 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Mon, 11 Mar 2024 13:23:54 +0000 Subject: [PATCH] build based on 6778507 --- dev/.documenter-siteinfo.json | 2 +- dev/api/index.html | 14 +- dev/assets/documenter.js | 923 +++++++++++++++----------- dev/assets/themes/documenter-dark.css | 2 +- dev/index.html | 2 +- dev/objects.inv | 9 + dev/search_index.js | 2 +- 7 files changed, 562 insertions(+), 392 deletions(-) create mode 100644 dev/objects.inv diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 201bce5..6789c27 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.6.7","generation_timestamp":"2024-02-07T17:55:32","documenter_version":"1.2.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.6.7","generation_timestamp":"2024-03-11T13:23:49","documenter_version":"1.3.0"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index b42f45e..b65a73d 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -64,7 +64,7 @@ Foo("hello", (40, 50, "hello"))

The behaviour when the same node appears twice can be altered by giving a value to the prune keyword, which is then used in place of all but the first:

julia> twice = [1, 2];
 
 julia> fmap(float, (x = twice, y = [1,2], z = twice); prune = missing)
-(x = [1.0, 2.0], y = [1.0, 2.0], z = missing)
source
Functors.@functorMacro
@functor T
+(x = [1.0, 2.0], y = [1.0, 2.0], z = missing)
source
Functors.@functorMacro
@functor T
 @functor T (x,)

Adds methods to functor allowing recursion into objects of type T, and reconstruction. Assumes that T has a constructor accepting all of its fields, which is true unless you have provided an inner constructor which does not.

By default all fields of T are considered children; this can be restricted be restructed by providing a tuple of field names.

Examples

julia> struct Foo; x; y; end
 
 julia> @functor Foo
@@ -90,7 +90,7 @@
 TwoThirds("ten", 20, "thirty")
 
 julia> fmap(x -> 10x, TwoThirds(Foo(1,2), Foo(3,4), 56))
-TwoThirds(Foo(10, 20), Foo(3, 4), 560)
source
Functors.@leafMacro
@leaf T

Define functor for the type T so that isleaf(x::T) == true.

source
Functors.functorFunction
Functors.functor(x) = functor(typeof(x), x)

Returns a tuple containing, first, a NamedTuple of the children of x (typically its fields), and second, a reconstruction funciton. This controls the behaviour of fmap.

Methods should be added to functor(::Type{T}, x) for custom types, usually using the macro @functor.

source
Functors.childrenFunction
Functors.children(x)

Return the children of x as defined by functor. Equivalent to functor(x)[1].

source
Functors.isleafFunction
Functors.isleaf(x)

Return true if x has no children according to functor.

Examples

julia> Functors.isleaf(1)
+TwoThirds(Foo(10, 20), Foo(3, 4), 560)
source
Functors.@leafMacro
@leaf T

Define functor for the type T so that isleaf(x::T) == true.

source
Functors.functorFunction
Functors.functor(x) = functor(typeof(x), x)

Returns a tuple containing, first, a NamedTuple of the children of x (typically its fields), and second, a reconstruction funciton. This controls the behaviour of fmap.

Methods should be added to functor(::Type{T}, x) for custom types, usually using the macro @functor.

source
Functors.childrenFunction
Functors.children(x)

Return the children of x as defined by functor. Equivalent to functor(x)[1].

source
Functors.isleafFunction
Functors.isleaf(x)

Return true if x has no children according to functor.

Examples

julia> Functors.isleaf(1)
 true
 
 julia> Functors.isleaf([2, 3, 4])
@@ -106,11 +106,11 @@
 false
 
 julia> Functors.isleaf(())
-true
source
Functors.AbstractWalkType
AbstractWalk

Any walk for use with fmap should inherit from this type. A walk subtyping AbstractWalk must satisfy the walk function interface:

struct MyWalk <: AbstractWalk end
+true
source
Functors.AbstractWalkType
AbstractWalk

Any walk for use with fmap should inherit from this type. A walk subtyping AbstractWalk must satisfy the walk function interface:

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.

source
Functors.executeFunction
execute(walk, x, ys...)

Execute a walk that recursively calls itself, starting at a node x in a Functors tree, as well as optional associated nodes ys... in other Functors trees. Any custom walk function that subtypes Functors.AbstractWalk is permitted.

source
Functors.DefaultWalkType
DefaultWalk()

The default walk behavior for Functors.jl. Walks all the Functors.children of trees (x, ys...) based on the structure of x. The resulting mapped child nodes are restructured into the type of x.

See fmap for more information.

source
Functors.StructuralWalkType
StructuralWalk()

A structural variant of Functors.DefaultWalk. The recursion behavior is identical, but the mapped children are not restructured.

See fmapstructure for more information.

source
Functors.ExcludeWalkType
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.

source
Functors.CachedWalkType
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.

source
Functors.CollectWalkType
CollectWalk()

A walk that recurses into a node x via Functors.children, storing the recursion history in a cache. The resulting ordered recursion history is returned.

See fcollect for more information.

source
Functors.AnonymousWalkType
AnonymousWalk(walk_fn)

Wrap a walk_fn so that AnonymousWalk(walk_fn) isa AbstractWalk. This type only exists for backwards compatability and should not be directly used. Attempting to wrap an existing AbstractWalk is a no-op (i.e. it is not wrapped).

source
Functors.IterateWalkType
IterateWalk()

A walk that walks all the Functors.children of trees (x, ys...) and concatenates the iterators of the children via Iterators.flatten. The resulting iterator is returned.

When used with fmap, the provided function f should return an iterator. For example, to iterate through the square of every scalar value:

julia> x = ([1, 2, 3], 4, (5, 6, [7, 8]));
+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.

source
Functors.executeFunction
execute(walk, x, ys...)

Execute a walk that recursively calls itself, starting at a node x in a Functors tree, as well as optional associated nodes ys... in other Functors trees. Any custom walk function that subtypes Functors.AbstractWalk is permitted.

source
Functors.DefaultWalkType
DefaultWalk()

The default walk behavior for Functors.jl. Walks all the Functors.children of trees (x, ys...) based on the structure of x. The resulting mapped child nodes are restructured into the type of x.

See fmap for more information.

source
Functors.StructuralWalkType
StructuralWalk()

A structural variant of Functors.DefaultWalk. The recursion behavior is identical, but the mapped children are not restructured.

See fmapstructure for more information.

source
Functors.ExcludeWalkType
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.

source
Functors.CachedWalkType
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.

source
Functors.CollectWalkType
CollectWalk()

A walk that recurses into a node x via Functors.children, storing the recursion history in a cache. The resulting ordered recursion history is returned.

See fcollect for more information.

source
Functors.AnonymousWalkType
AnonymousWalk(walk_fn)

Wrap a walk_fn so that AnonymousWalk(walk_fn) isa AbstractWalk. This type only exists for backwards compatability and should not be directly used. Attempting to wrap an existing AbstractWalk is a no-op (i.e. it is not wrapped).

source
Functors.IterateWalkType
IterateWalk()

A walk that walks all the Functors.children of trees (x, ys...) and concatenates the iterators of the children via Iterators.flatten. The resulting iterator is returned.

When used with fmap, the provided function f should return an iterator. For example, to iterate through the square of every scalar value:

julia> x = ([1, 2, 3], 4, (5, 6, [7, 8]));
 
 julia> make_iterator(x) = x isa AbstractVector ? x.^2 : (x^2,);
 
@@ -140,7 +140,7 @@
  (25, 16)
  (36, 9)
  (49, 4)
- (64, 1)
source
Functors.fmapstructureFunction
fmapstructure(f, x; exclude = isleaf)

Like fmap, but doesn't preserve the type of custom structs. Instead, it returns a NamedTuple (or a Tuple, or an array), or a nested set of these.

Useful for when the output must not contain custom structs.

Examples

julia> struct Foo; x; y; end
+ (64, 1)
source
Functors.fmapstructureFunction
fmapstructure(f, x; exclude = isleaf)

Like fmap, but doesn't preserve the type of custom structs. Instead, it returns a NamedTuple (or a Tuple, or an array), or a nested set of these.

Useful for when the output must not contain custom structs.

Examples

julia> struct Foo; x; y; end
 
 julia> @functor Foo
 
@@ -156,7 +156,7 @@
 6
 7
 8
-(x = nothing, y = Any[nothing, (nothing, nothing), (x = nothing, y = nothing)])
source
Functors.fcollectFunction
fcollect(x; exclude = v -> false)

Traverse x by recursing each child of x as defined by functor and collecting the results into a flat array, ordered by a breadth-first traversal of x, respecting the iteration order of children calls.

Doesn't recurse inside branches rooted at nodes v for which exclude(v) == true. In such cases, the root v is also excluded from the result. By default, exclude always yields false.

See also children.

Examples

julia> struct Foo; x; y; end
+(x = nothing, y = Any[nothing, (nothing, nothing), (x = nothing, y = nothing)])
source
Functors.fcollectFunction
fcollect(x; exclude = v -> false)

Traverse x by recursing each child of x as defined by functor and collecting the results into a flat array, ordered by a breadth-first traversal of x, respecting the iteration order of children calls.

Doesn't recurse inside branches rooted at nodes v for which exclude(v) == true. In such cases, the root v is also excluded from the result. By default, exclude always yields false.

See also children.

Examples

julia> struct Foo; x; y; end
 
 julia> @functor Foo
 
@@ -184,4 +184,4 @@
 julia> fcollect(m, exclude = v -> Functors.isleaf(v))
 2-element Vector{Any}:
  Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))
- Bar([1, 2, 3])
source
+ Bar([1, 2, 3])source
Functors.fleavesFunction
fleaves(x; exclude = isleaf)

Traverse x by recursing each child of x as defined by functor and collecting the leaves into a flat array, ordered by a breadth-first traversal of x, respecting the iteration order of children calls.

The exclude function is used to determine whether to recurse into a node, therefore identifying the leaves as the nodes for which exclude returns true.

See also fcollect for a similar function that collects all nodes instead.

Examples

```jldoctest julia> struct Bar; x; end

julia> @functor Bar

julia> struct TypeWithNoChildren; x; y; end

julia> m = (a=Bar([1,2,3]), b=TypeWithNoChildren(4, 5))

julia> fleaves(m) 2-element Vector{Any}: [1, 2, 3] TypeWithNoChildren(:a, :b)

source
diff --git a/dev/assets/documenter.js b/dev/assets/documenter.js index f531160..c6562b5 100644 --- a/dev/assets/documenter.js +++ b/dev/assets/documenter.js @@ -4,7 +4,6 @@ requirejs.config({ 'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia.min', 'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/headroom.min', 'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min', - 'minisearch': 'https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min', 'katex-auto-render': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min', 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min', 'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/jQuery.headroom.min', @@ -103,9 +102,10 @@ $(document).on("click", ".docstring header", function () { }); }); -$(document).on("click", ".docs-article-toggle-button", function () { +$(document).on("click", ".docs-article-toggle-button", function (event) { let articleToggleTitle = "Expand docstring"; let navArticleToggleTitle = "Expand all docstrings"; + let animationSpeed = event.noToggleAnimation ? 0 : 400; debounce(() => { if (isExpanded) { @@ -116,7 +116,7 @@ $(document).on("click", ".docs-article-toggle-button", function () { isExpanded = false; - $(".docstring section").slideUp(); + $(".docstring section").slideUp(animationSpeed); } else { $(this).removeClass("fa-chevron-down").addClass("fa-chevron-up"); $(".docstring-article-toggle-button") @@ -127,7 +127,7 @@ $(document).on("click", ".docs-article-toggle-button", function () { articleToggleTitle = "Collapse docstring"; navArticleToggleTitle = "Collapse all docstrings"; - $(".docstring section").slideDown(); + $(".docstring section").slideDown(animationSpeed); } $(this).prop("title", navArticleToggleTitle); @@ -224,224 +224,465 @@ $(document).ready(function () { }) //////////////////////////////////////////////////////////////////////////////// -require(['jquery', 'minisearch'], function($, minisearch) { - -// In general, most search related things will have "search" as a prefix. -// To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc +require(['jquery'], function($) { -let results = []; -let timer = undefined; +$(document).ready(function () { + let meta = $("div[data-docstringscollapsed]").data(); -let data = documenterSearchIndex["docs"].map((x, key) => { - x["id"] = key; // minisearch requires a unique for each object - return x; + if (meta?.docstringscollapsed) { + $("#documenter-article-toggle-button").trigger({ + type: "click", + noToggleAnimation: true, + }); + } }); -// list below is the lunr 2.1.3 list minus the intersect with names(Base) -// (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) -// ideally we'd just filter the original list but it's not available as a variable -const stopWords = new Set([ - "a", - "able", - "about", - "across", - "after", - "almost", - "also", - "am", - "among", - "an", - "and", - "are", - "as", - "at", - "be", - "because", - "been", - "but", - "by", - "can", - "cannot", - "could", - "dear", - "did", - "does", - "either", - "ever", - "every", - "from", - "got", - "had", - "has", - "have", - "he", - "her", - "hers", - "him", - "his", - "how", - "however", - "i", - "if", - "into", - "it", - "its", - "just", - "least", - "like", - "likely", - "may", - "me", - "might", - "most", - "must", - "my", - "neither", - "no", - "nor", - "not", - "of", - "off", - "often", - "on", - "or", - "other", - "our", - "own", - "rather", - "said", - "say", - "says", - "she", - "should", - "since", - "so", - "some", - "than", - "that", - "the", - "their", - "them", - "then", - "there", - "these", - "they", - "this", - "tis", - "to", - "too", - "twas", - "us", - "wants", - "was", - "we", - "were", - "what", - "when", - "who", - "whom", - "why", - "will", - "would", - "yet", - "you", - "your", -]); - -let index = new minisearch({ - fields: ["title", "text"], // fields to index for full-text search - storeFields: ["location", "title", "text", "category", "page"], // fields to return with search results - processTerm: (term) => { - let word = stopWords.has(term) ? null : term; - if (word) { - // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names - word = word - .replace(/^[^a-zA-Z0-9@!]+/, "") - .replace(/[^a-zA-Z0-9@!]+$/, ""); - } +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { - return word ?? null; - }, - // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not find anything if searching for "add!", only for the entire qualification - tokenize: (string) => string.split(/[\s\-\.]+/), - // options which will be applied during the search - searchOptions: { - boost: { title: 100 }, - fuzzy: 2, +/* +To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc + +PSEUDOCODE: + +Searching happens automatically as the user types or adjusts the selected filters. +To preserve responsiveness, as much as possible of the slow parts of the search are done +in a web worker. Searching and result generation are done in the worker, and filtering and +DOM updates are done in the main thread. The filters are in the main thread as they should +be very quick to apply. This lets filters be changed without re-searching with minisearch +(which is possible even if filtering is on the worker thread) and also lets filters be +changed _while_ the worker is searching and without message passing (neither of which are +possible if filtering is on the worker thread) + +SEARCH WORKER: + +Import minisearch + +Build index + +On message from main thread + run search + find the first 200 unique results from each category, and compute their divs for display + note that this is necessary and sufficient information for the main thread to find the + first 200 unique results from any given filter set + post results to main thread + +MAIN: + +Launch worker + +Declare nonconstant globals (worker_is_running, last_search_text, unfiltered_results) + +On text update + if worker is not running, launch_search() + +launch_search + set worker_is_running to true, set last_search_text to the search text + post the search query to worker + +on message from worker + if last_search_text is not the same as the text in the search field, + the latest search result is not reflective of the latest search query, so update again + launch_search() + otherwise + set worker_is_running to false + + regardless, display the new search results to the user + save the unfiltered_results as a global + update_search() + +on filter click + adjust the filter selection + update_search() + +update_search + apply search filters by looping through the unfiltered_results and finding the first 200 + unique results that match the filters + + Update the DOM +*/ + +/////// SEARCH WORKER /////// + +function worker_function(documenterSearchIndex, documenterBaseURL, filters) { + importScripts( + "https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min.js" + ); + + let data = documenterSearchIndex.map((x, key) => { + x["id"] = key; // minisearch requires a unique for each object + return x; + }); + + // list below is the lunr 2.1.3 list minus the intersect with names(Base) + // (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) + // ideally we'd just filter the original list but it's not available as a variable + const stopWords = new Set([ + "a", + "able", + "about", + "across", + "after", + "almost", + "also", + "am", + "among", + "an", + "and", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "does", + "either", + "ever", + "every", + "from", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "into", + "it", + "its", + "just", + "least", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "who", + "whom", + "why", + "will", + "would", + "yet", + "you", + "your", + ]); + + let index = new MiniSearch({ + fields: ["title", "text"], // fields to index for full-text search + storeFields: ["location", "title", "text", "category", "page"], // fields to return with results processTerm: (term) => { let word = stopWords.has(term) ? null : term; if (word) { + // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names word = word .replace(/^[^a-zA-Z0-9@!]+/, "") .replace(/[^a-zA-Z0-9@!]+$/, ""); + + word = word.toLowerCase(); } return word ?? null; }, + // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not + // find anything if searching for "add!", only for the entire qualification tokenize: (string) => string.split(/[\s\-\.]+/), - }, -}); + // options which will be applied during the search + searchOptions: { + prefix: true, + boost: { title: 100 }, + fuzzy: 2, + }, + }); -index.addAll(data); + index.addAll(data); + + /** + * Used to map characters to HTML entities. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const htmlEscapes = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }; + + /** + * Used to match HTML entities and HTML characters. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const reUnescapedHtml = /[&<>"']/g; + const reHasUnescapedHtml = RegExp(reUnescapedHtml.source); + + /** + * Escape function from lodash + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + function escape(string) { + return string && reHasUnescapedHtml.test(string) + ? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr]) + : string || ""; + } -let filters = [...new Set(data.map((x) => x.category))]; -var modal_filters = make_modal_body_filters(filters); -var filter_results = []; + /** + * Make the result component given a minisearch result data object and the value + * of the search input as queryString. To view the result object structure, refer: + * https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult + * + * @param {object} result + * @param {string} querystring + * @returns string + */ + function make_search_result(result, querystring) { + let search_divider = `
`; + let display_link = + result.location.slice(Math.max(0), Math.min(50, result.location.length)) + + (result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div + + if (result.page !== "") { + display_link += ` (${result.page})`; + } -$(document).on("keyup", ".documenter-search-input", function (event) { - // Adding a debounce to prevent disruptions from super-speed typing! - debounce(() => update_search(filter_results), 300); + let textindex = new RegExp(`${querystring}`, "i").exec(result.text); + let text = + textindex !== null + ? result.text.slice( + Math.max(textindex.index - 100, 0), + Math.min( + textindex.index + querystring.length + 100, + result.text.length + ) + ) + : ""; // cut-off text before and after from the match + + text = text.length ? escape(text) : ""; + + let display_result = text.length + ? "..." + + text.replace( + new RegExp(`${escape(querystring)}`, "i"), // For first occurrence + '$&' + ) + + "..." + : ""; // highlights the match + + let in_code = false; + if (!["page", "section"].includes(result.category.toLowerCase())) { + in_code = true; + } + + // We encode the full url to escape some special characters which can lead to broken links + let result_div = ` + +
+
${escape(result.title)}
+
${result.category}
+
+

+ ${display_result} +

+
+ ${display_link} +
+
+ ${search_divider} + `; + + return result_div; + } + + self.onmessage = function (e) { + let query = e.data; + let results = index.search(query, { + filter: (result) => { + // Only return relevant results + return result.score >= 1; + }, + }); + + // Pre-filter to deduplicate and limit to 200 per category to the extent + // possible without knowing what the filters are. + let filtered_results = []; + let counts = {}; + for (let filter of filters) { + counts[filter] = 0; + } + let present = {}; + + for (let result of results) { + cat = result.category; + cnt = counts[cat]; + if (cnt < 200) { + id = cat + "---" + result.location; + if (present[id]) { + continue; + } + present[id] = true; + filtered_results.push({ + location: result.location, + category: cat, + div: make_search_result(result, query), + }); + } + } + + postMessage(filtered_results); + }; +} + +// `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript! +const filters = [ + ...new Set(documenterSearchIndex["docs"].map((x) => x.category)), +]; +const worker_str = + "(" + + worker_function.toString() + + ")(" + + JSON.stringify(documenterSearchIndex["docs"]) + + "," + + JSON.stringify(documenterBaseURL) + + "," + + JSON.stringify(filters) + + ")"; +const worker_blob = new Blob([worker_str], { type: "text/javascript" }); +const worker = new Worker(URL.createObjectURL(worker_blob)); + +/////// SEARCH MAIN /////// + +// Whether the worker is currently handling a search. This is a boolean +// as the worker only ever handles 1 or 0 searches at a time. +var worker_is_running = false; + +// The last search text that was sent to the worker. This is used to determine +// if the worker should be launched again when it reports back results. +var last_search_text = ""; + +// The results of the last search. This, in combination with the state of the filters +// in the DOM, is used compute the results to display on calls to update_search. +var unfiltered_results = []; + +// Which filter is currently selected +var selected_filter = ""; + +$(document).on("input", ".documenter-search-input", function (event) { + if (!worker_is_running) { + launch_search(); + } }); +function launch_search() { + worker_is_running = true; + last_search_text = $(".documenter-search-input").val(); + worker.postMessage(last_search_text); +} + +worker.onmessage = function (e) { + if (last_search_text !== $(".documenter-search-input").val()) { + launch_search(); + } else { + worker_is_running = false; + } + + unfiltered_results = e.data; + update_search(); +}; + $(document).on("click", ".search-filter", function () { if ($(this).hasClass("search-filter-selected")) { - $(this).removeClass("search-filter-selected"); + selected_filter = ""; } else { - $(this).addClass("search-filter-selected"); + selected_filter = $(this).text().toLowerCase(); } - // Adding a debounce to prevent disruptions from crazy clicking! - debounce(() => get_filters(), 300); + // This updates search results and toggles classes for UI: + update_search(); }); -/** - * A debounce function, takes a function and an optional timeout in milliseconds - * - * @function callback - * @param {number} timeout - */ -function debounce(callback, timeout = 300) { - clearTimeout(timer); - timer = setTimeout(callback, timeout); -} - /** * Make/Update the search component - * - * @param {string[]} selected_filters */ -function update_search(selected_filters = []) { - let initial_search_body = ` -
Type something to get started!
- `; - +function update_search() { let querystring = $(".documenter-search-input").val(); if (querystring.trim()) { - results = index.search(querystring, { - filter: (result) => { - // Filtering results - if (selected_filters.length === 0) { - return result.score >= 1; - } else { - return ( - result.score >= 1 && selected_filters.includes(result.category) - ); - } - }, - }); + if (selected_filter == "") { + results = unfiltered_results; + } else { + results = unfiltered_results.filter((result) => { + return selected_filter == result.category.toLowerCase(); + }); + } let search_result_container = ``; + let modal_filters = make_modal_body_filters(); let search_divider = `
`; if (results.length) { @@ -449,19 +690,23 @@ function update_search(selected_filters = []) { let count = 0; let search_results = ""; - results.forEach(function (result) { - if (result.location) { - // Checking for duplication of results for the same page - if (!links.includes(result.location)) { - search_results += make_search_result(result, querystring); - count++; - } - + for (var i = 0, n = results.length; i < n && count < 200; ++i) { + let result = results[i]; + if (result.location && !links.includes(result.location)) { + search_results += result.div; + count++; links.push(result.location); } - }); + } - let result_count = `
${count} result(s)
`; + if (count == 1) { + count_str = "1 result"; + } else if (count == 200) { + count_str = "200+ results"; + } else { + count_str = count + " results"; + } + let result_count = `
${count_str}
`; search_result_container = `
@@ -490,125 +735,37 @@ function update_search(selected_filters = []) { $(".search-modal-card-body").html(search_result_container); } else { - filter_results = []; - modal_filters = make_modal_body_filters(filters, filter_results); - if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { $(".search-modal-card-body").addClass("is-justify-content-center"); } - $(".search-modal-card-body").html(initial_search_body); + $(".search-modal-card-body").html(` +
Type something to get started!
+ `); } } /** * Make the modal filter html * - * @param {string[]} filters - * @param {string[]} selected_filters * @returns string */ -function make_modal_body_filters(filters, selected_filters = []) { - let str = ``; - - filters.forEach((val) => { - if (selected_filters.includes(val)) { - str += `${val}`; - } else { - str += `${val}`; - } - }); +function make_modal_body_filters() { + let str = filters + .map((val) => { + if (selected_filter == val.toLowerCase()) { + return `${val}`; + } else { + return `${val}`; + } + }) + .join(""); - let filter_html = ` + return `
Filters: ${str} -
- `; - - return filter_html; -} - -/** - * Make the result component given a minisearch result data object and the value of the search input as queryString. - * To view the result object structure, refer: https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult - * - * @param {object} result - * @param {string} querystring - * @returns string - */ -function make_search_result(result, querystring) { - let search_divider = `
`; - let display_link = - result.location.slice(Math.max(0), Math.min(50, result.location.length)) + - (result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div - - if (result.page !== "") { - display_link += ` (${result.page})`; - } - - let textindex = new RegExp(`\\b${querystring}\\b`, "i").exec(result.text); - let text = - textindex !== null - ? result.text.slice( - Math.max(textindex.index - 100, 0), - Math.min( - textindex.index + querystring.length + 100, - result.text.length - ) - ) - : ""; // cut-off text before and after from the match - - let display_result = text.length - ? "..." + - text.replace( - new RegExp(`\\b${querystring}\\b`, "i"), // For first occurrence - '$&' - ) + - "..." - : ""; // highlights the match - - let in_code = false; - if (!["page", "section"].includes(result.category.toLowerCase())) { - in_code = true; - } - - // We encode the full url to escape some special characters which can lead to broken links - let result_div = ` - -
-
${result.title}
-
${result.category}
-
-

- ${display_result} -

-
- ${display_link} -
-
- ${search_divider} - `; - - return result_div; -} - -/** - * Get selected filters, remake the filter html and lastly update the search modal - */ -function get_filters() { - let ele = $(".search-filters .search-filter-selected").get(); - filter_results = ele.map((x) => $(x).text().toLowerCase()); - modal_filters = make_modal_body_filters(filters, filter_results); - update_search(filter_results); +
`; } }) @@ -635,103 +792,107 @@ $(document).ready(function () { //////////////////////////////////////////////////////////////////////////////// require(['jquery'], function($) { -let search_modal_header = ` - -`; - -let initial_search_body = ` -
Type something to get started!
-`; - -let search_modal_footer = ` - -`; - -$(document.body).append( - ` - +Baz(1.0, 2)

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.

Appropriate Use

Not everything should be a functor!

Due to its generic nature it is very attractive to mark several structures as @functor when it may not be quite safe to do so.

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.

Examples of this include element types of arrays which typically have their own mathematical operations defined. Adding a @functor to such a type would end up missing methods such as +(::MyElementType, ::MyElementType). Think RGB from Colors.jl.

diff --git a/dev/objects.inv b/dev/objects.inv new file mode 100644 index 0000000..2321b15 --- /dev/null +++ b/dev/objects.inv @@ -0,0 +1,9 @@ +# Sphinx inventory version 2 +# Project: Functors.jl +# Version: 0.4.7 +# The remainder of this file is compressed using zlib. +xN0D +K-KXu!!־.7J'!$s3QZb|iW= j~>/XTQ`??PNC՚rВW%lPAg g?Lx"'mi@8Æord+D +މ];!Rr5!| +q(1M~b'XLPtil[C"9(Bt@yK{-}Z:,$C] +XÌYGlQgOAN \ No newline at end of file diff --git a/dev/search_index.js b/dev/search_index.js index 59bb070..f54647a 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"api/","page":"API","title":"API","text":"Functors.fmap\nFunctors.@functor\nFunctors.@leaf","category":"page"},{"location":"api/#Functors.fmap","page":"API","title":"Functors.fmap","text":"fmap(f, x, ys...; exclude = Functors.isleaf, walk = Functors.DefaultWalk()[, prune])\n\nA structure and type preserving map.\n\nBy default it transforms every leaf node (identified by exclude, default isleaf) by applying f, and otherwise traverses x recursively using functor. Optionally, it may also be associated with objects ys with the same tree structure. In that case, f is applied to the corresponding leaf nodes in x and ys.\n\nExamples\n\njulia> fmap(string, (x=1, y=(2, 3)))\n(x = \"1\", y = (\"2\", \"3\"))\n\njulia> nt = (a = [1,2], b = [23, (45,), (x=6//7, y=())], c = [8,9]);\n\njulia> fmap(println, nt)\n[1, 2]\n23\n45\n6//7\n()\n[8, 9]\n(a = nothing, b = Any[nothing, (nothing,), (x = nothing, y = nothing)], c = nothing)\n\njulia> fmap(println, nt; exclude = x -> x isa Array)\n[1, 2]\nAny[23, (45,), (x = 6//7, y = ())]\n[8, 9]\n(a = nothing, b = nothing, c = nothing)\n\njulia> twice = [1, 2]; # println only acts once on this\n\njulia> fmap(println, (i = twice, ii = 34, iii = [5, 6], iv = (twice, 34), v = 34.0))\n[1, 2]\n34\n[5, 6]\n34\n34.0\n(i = nothing, ii = nothing, iii = nothing, iv = (nothing, nothing), v = nothing)\n\njulia> d1 = Dict(\"x\" => [1,2], \"y\" => 3);\n\njulia> d2 = Dict(\"x\" => [4,5], \"y\" => 6, \"z\" => \"an_extra_value\");\n\njulia> fmap(+, d1, d2) == Dict(\"x\" => [5, 7], \"y\" => 9) # Note that \"z\" is ignored\ntrue\n\nMutable objects which appear more than once are only handled once (by caching f(x) in an IdDict). Thus the relationship x.i === x.iv[1] will be preserved. An immutable object which appears twice is not stored in the cache, thus f(34) will be called twice, and the results will agree only if f is pure.\n\nBy default, Tuples, NamedTuples, and some other container-like types in Base have children to recurse into. Arrays of numbers do not. To enable recursion into new types, you must provide a method of functor, which can be done using the macro @functor:\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> struct Bar; x; end\n\njulia> @functor Bar\n\njulia> m = Foo(Bar([1,2,3]), (4, 5, Bar(Foo(6, 7))));\n\njulia> fmap(x -> 10x, m)\nFoo(Bar([10, 20, 30]), (40, 50, Bar(Foo(60, 70))))\n\njulia> fmap(string, m)\nFoo(Bar(\"[1, 2, 3]\"), (\"4\", \"5\", Bar(Foo(\"6\", \"7\"))))\n\njulia> fmap(string, m, exclude = v -> v isa Bar)\nFoo(\"Bar([1, 2, 3])\", (4, 5, \"Bar(Foo(6, 7))\"))\n\nTo recurse into custom types without reconstructing them afterwards, use fmapstructure.\n\nFor advanced customization of the traversal behaviour, pass a custom walk function that subtypes Functors.AbstractWalk. The call fmap(f, x, ys...; walk = mywalk) will wrap mywalk in ExcludeWalk then CachedWalk. Here, ExcludeWalk is responsible for applying f at excluded nodes. For a low-level interface for executing a user-constructed walk, see execute.\n\njulia> struct MyWalk <: Functors.AbstractWalk end\n\njulia> (::MyWalk)(recurse, x) = x isa Bar ? \"hello\" :\n Functors.DefaultWalk()(recurse, x)\n\njulia> fmap(x -> 10x, m; walk = MyWalk())\nFoo(\"hello\", (40, 50, \"hello\"))\n\nThe behaviour when the same node appears twice can be altered by giving a value to the prune keyword, which is then used in place of all but the first:\n\njulia> twice = [1, 2];\n\njulia> fmap(float, (x = twice, y = [1,2], z = twice); prune = missing)\n(x = [1.0, 2.0], y = [1.0, 2.0], z = missing)\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.@functor","page":"API","title":"Functors.@functor","text":"@functor T\n@functor T (x,)\n\nAdds methods to functor allowing recursion into objects of type T, and reconstruction. Assumes that T has a constructor accepting all of its fields, which is true unless you have provided an inner constructor which does not.\n\nBy default all fields of T are considered children; this can be restricted be restructed by providing a tuple of field names.\n\nExamples\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> Functors.children(Foo(1,2))\n(x = 1, y = 2)\n\njulia> _, re = Functors.functor(Foo(1,2));\n\njulia> re((10, 20))\nFoo(10, 20)\n\njulia> struct TwoThirds a; b; c; end\n\njulia> @functor TwoThirds (a, c)\n\njulia> ch2, re3 = Functors.functor(TwoThirds(10,20,30));\n\njulia> ch2\n(a = 10, c = 30)\n\njulia> re3((\"ten\", \"thirty\"))\nTwoThirds(\"ten\", 20, \"thirty\")\n\njulia> fmap(x -> 10x, TwoThirds(Foo(1,2), Foo(3,4), 56))\nTwoThirds(Foo(10, 20), Foo(3, 4), 560)\n\n\n\n\n\n","category":"macro"},{"location":"api/#Functors.@leaf","page":"API","title":"Functors.@leaf","text":"@leaf T\n\nDefine functor for the type T so that isleaf(x::T) == true.\n\n\n\n\n\n","category":"macro"},{"location":"api/","page":"API","title":"API","text":"Functors.functor\nFunctors.children\nFunctors.isleaf","category":"page"},{"location":"api/#Functors.functor","page":"API","title":"Functors.functor","text":"Functors.functor(x) = functor(typeof(x), x)\n\nReturns a tuple containing, first, a NamedTuple of the children of x (typically its fields), and second, a reconstruction funciton. This controls the behaviour of fmap.\n\nMethods should be added to functor(::Type{T}, x) for custom types, usually using the macro @functor.\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.children","page":"API","title":"Functors.children","text":"Functors.children(x)\n\nReturn the children of x as defined by functor. Equivalent to functor(x)[1].\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.isleaf","page":"API","title":"Functors.isleaf","text":"Functors.isleaf(x)\n\nReturn true if x has no children according to functor.\n\nExamples\n\njulia> Functors.isleaf(1)\ntrue\n\njulia> Functors.isleaf([2, 3, 4])\ntrue\n\njulia> Functors.isleaf([\"five\", [6, 7]])\nfalse\n\njulia> Functors.isleaf([])\nfalse\n\njulia> Functors.isleaf((8, 9))\nfalse\n\njulia> Functors.isleaf(())\ntrue\n\n\n\n\n\n","category":"function"},{"location":"api/","page":"API","title":"API","text":"Functors.AbstractWalk\nFunctors.execute\nFunctors.DefaultWalk\nFunctors.StructuralWalk\nFunctors.ExcludeWalk\nFunctors.CachedWalk\nFunctors.CollectWalk\nFunctors.AnonymousWalk\nFunctors.IterateWalk","category":"page"},{"location":"api/#Functors.AbstractWalk","page":"API","title":"Functors.AbstractWalk","text":"AbstractWalk\n\nAny walk for use with fmap should inherit from this type. A walk subtyping AbstractWalk must satisfy the walk function interface:\n\nstruct MyWalk <: AbstractWalk end\n\nfunction (::MyWalk)(recurse, x, ys...)\n # implement this\nend\n\nThe 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.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.execute","page":"API","title":"Functors.execute","text":"execute(walk, x, ys...)\n\nExecute a walk that recursively calls itself, starting at a node x in a Functors tree, as well as optional associated nodes ys... in other Functors trees. Any custom walk function that subtypes Functors.AbstractWalk is permitted.\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.DefaultWalk","page":"API","title":"Functors.DefaultWalk","text":"DefaultWalk()\n\nThe default walk behavior for Functors.jl. Walks all the Functors.children of trees (x, ys...) based on the structure of x. The resulting mapped child nodes are restructured into the type of x.\n\nSee fmap for more information.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.StructuralWalk","page":"API","title":"Functors.StructuralWalk","text":"StructuralWalk()\n\nA structural variant of Functors.DefaultWalk. The recursion behavior is identical, but the mapped children are not restructured.\n\nSee fmapstructure for more information.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.ExcludeWalk","page":"API","title":"Functors.ExcludeWalk","text":"ExcludeWalk(walk, fn, exclude)\n\nA 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.\n\nTypically wraps an existing walk for use with fmap.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.CachedWalk","page":"API","title":"Functors.CachedWalk","text":"CachedWalk(walk[; prune])\n\nA 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:\n\nprune is specified, then it is returned, or\nprune is unspecified, and the previously cached recursion of (x, ys...) returned.\n\nTypically wraps an existing walk for use with fmap.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.CollectWalk","page":"API","title":"Functors.CollectWalk","text":"CollectWalk()\n\nA walk that recurses into a node x via Functors.children, storing the recursion history in a cache. The resulting ordered recursion history is returned.\n\nSee fcollect for more information.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.AnonymousWalk","page":"API","title":"Functors.AnonymousWalk","text":"AnonymousWalk(walk_fn)\n\nWrap a walk_fn so that AnonymousWalk(walk_fn) isa AbstractWalk. This type only exists for backwards compatability and should not be directly used. Attempting to wrap an existing AbstractWalk is a no-op (i.e. it is not wrapped).\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.IterateWalk","page":"API","title":"Functors.IterateWalk","text":"IterateWalk()\n\nA walk that walks all the Functors.children of trees (x, ys...) and concatenates the iterators of the children via Iterators.flatten. The resulting iterator is returned.\n\nWhen used with fmap, the provided function f should return an iterator. For example, to iterate through the square of every scalar value:\n\njulia> x = ([1, 2, 3], 4, (5, 6, [7, 8]));\n\njulia> make_iterator(x) = x isa AbstractVector ? x.^2 : (x^2,);\n\njulia> iter = fmap(make_iterator, x; walk=Functors.IterateWalk(), cache=nothing);\n\njulia> collect(iter)\n8-element Vector{Int64}:\n 1\n 4\n 9\n 16\n 25\n 36\n 49\n 64\n\nWe can also simultaneously iterate through multiple functors:\n\njulia> y = ([8, 7, 6], 5, (4, 3, [2, 1]));\n\njulia> make_zipped_iterator(x, y) = zip(make_iterator(x), make_iterator(y));\n\njulia> zipped_iter = fmap(make_zipped_iterator, x, y; walk=Functors.IterateWalk(), cache=nothing);\n\njulia> collect(zipped_iter)\n8-element Vector{Tuple{Int64, Int64}}:\n (1, 64)\n (4, 49)\n (9, 36)\n (16, 25)\n (25, 16)\n (36, 9)\n (49, 4)\n (64, 1)\n\n\n\n\n\n","category":"type"},{"location":"api/","page":"API","title":"API","text":"Functors.fmapstructure\nFunctors.fcollect","category":"page"},{"location":"api/#Functors.fmapstructure","page":"API","title":"Functors.fmapstructure","text":"fmapstructure(f, x; exclude = isleaf)\n\nLike fmap, but doesn't preserve the type of custom structs. Instead, it returns a NamedTuple (or a Tuple, or an array), or a nested set of these.\n\nUseful for when the output must not contain custom structs.\n\nExamples\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> m = Foo([1,2,3], [4, (5, 6), Foo(7, 8)]);\n\njulia> fmapstructure(x -> 2x, m)\n(x = [2, 4, 6], y = Any[8, (10, 12), (x = 14, y = 16)])\n\njulia> fmapstructure(println, m)\n[1, 2, 3]\n4\n5\n6\n7\n8\n(x = nothing, y = Any[nothing, (nothing, nothing), (x = nothing, y = nothing)])\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.fcollect","page":"API","title":"Functors.fcollect","text":"fcollect(x; exclude = v -> false)\n\nTraverse x by recursing each child of x as defined by functor and collecting the results into a flat array, ordered by a breadth-first traversal of x, respecting the iteration order of children calls.\n\nDoesn't recurse inside branches rooted at nodes v for which exclude(v) == true. In such cases, the root v is also excluded from the result. By default, exclude always yields false.\n\nSee also children.\n\nExamples\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> struct Bar; x; end\n\njulia> @functor Bar\n\njulia> struct TypeWithNoChildren; x; y; end\n\njulia> m = Foo(Bar([1,2,3]), TypeWithNoChildren(:a, :b))\nFoo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n\njulia> fcollect(m)\n4-element Vector{Any}:\n Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n Bar([1, 2, 3])\n [1, 2, 3]\n TypeWithNoChildren(:a, :b)\n\njulia> fcollect(m, exclude = v -> v isa Bar)\n2-element Vector{Any}:\n Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n TypeWithNoChildren(:a, :b)\n\njulia> fcollect(m, exclude = v -> Functors.isleaf(v))\n2-element Vector{Any}:\n Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n Bar([1, 2, 3])\n\n\n\n\n\n","category":"function"},{"location":"#Functors.jl","page":"Home","title":"Functors.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Functors.jl provides a set of tools to represent functors. Functors are a powerful means to apply functions to generic objects without changing their structure.","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"#Basic-Usage-and-Implementation","page":"Home","title":"Basic Usage and Implementation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"When one marks a structure as @functor 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.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The workhorse of fmap is actually a lower level function, functor:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> using Functors\n\njulia> struct Foo\n x\n y\n end\n\njulia> @functor Foo\n\njulia> foo = Foo(1, [1, 2, 3]) # notice all the elements are integers\n\njulia> xs, re = Functors.functor(foo)\n((x = 1, y = [1, 2, 3]), var\"#21#22\"())\n\njulia> re(map(float, xs)) # element types have been switched out for floating point numbers\nFoo(1.0, [1.0, 2.0, 3.0])","category":"page"},{"location":"","page":"Home","title":"Home","text":"functor returns the parts of the object that can be inspected, as well as a reconstruction function (shown as re) that takes those values and restructures them back into an object of the original type.","category":"page"},{"location":"","page":"Home","title":"Home","text":"To include only certain fields of a struct, one can pass a tuple of field names to @functor:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> struct Baz\n x\n y\n end\n\njulia> @functor Baz (x,)\n\njulia> model = Baz(1, 2)\nBaz(1, 2)\n\njulia> fmap(float, model)\nBaz(1.0, 2)","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"#Appropriate-Use","page":"Home","title":"Appropriate Use","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"warning: Not everything should be a functor!\nDue to its generic nature it is very attractive to mark several structures as @functor when it may not be quite safe to do so.","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Examples of this include element types of arrays which typically have their own mathematical operations defined. Adding a @functor to such a type would end up missing methods such as +(::MyElementType, ::MyElementType). Think RGB from Colors.jl.","category":"page"}] +[{"location":"api/","page":"API","title":"API","text":"Functors.fmap\nFunctors.@functor\nFunctors.@leaf","category":"page"},{"location":"api/#Functors.fmap","page":"API","title":"Functors.fmap","text":"fmap(f, x, ys...; exclude = Functors.isleaf, walk = Functors.DefaultWalk()[, prune])\n\nA structure and type preserving map.\n\nBy default it transforms every leaf node (identified by exclude, default isleaf) by applying f, and otherwise traverses x recursively using functor. Optionally, it may also be associated with objects ys with the same tree structure. In that case, f is applied to the corresponding leaf nodes in x and ys.\n\nExamples\n\njulia> fmap(string, (x=1, y=(2, 3)))\n(x = \"1\", y = (\"2\", \"3\"))\n\njulia> nt = (a = [1,2], b = [23, (45,), (x=6//7, y=())], c = [8,9]);\n\njulia> fmap(println, nt)\n[1, 2]\n23\n45\n6//7\n()\n[8, 9]\n(a = nothing, b = Any[nothing, (nothing,), (x = nothing, y = nothing)], c = nothing)\n\njulia> fmap(println, nt; exclude = x -> x isa Array)\n[1, 2]\nAny[23, (45,), (x = 6//7, y = ())]\n[8, 9]\n(a = nothing, b = nothing, c = nothing)\n\njulia> twice = [1, 2]; # println only acts once on this\n\njulia> fmap(println, (i = twice, ii = 34, iii = [5, 6], iv = (twice, 34), v = 34.0))\n[1, 2]\n34\n[5, 6]\n34\n34.0\n(i = nothing, ii = nothing, iii = nothing, iv = (nothing, nothing), v = nothing)\n\njulia> d1 = Dict(\"x\" => [1,2], \"y\" => 3);\n\njulia> d2 = Dict(\"x\" => [4,5], \"y\" => 6, \"z\" => \"an_extra_value\");\n\njulia> fmap(+, d1, d2) == Dict(\"x\" => [5, 7], \"y\" => 9) # Note that \"z\" is ignored\ntrue\n\nMutable objects which appear more than once are only handled once (by caching f(x) in an IdDict). Thus the relationship x.i === x.iv[1] will be preserved. An immutable object which appears twice is not stored in the cache, thus f(34) will be called twice, and the results will agree only if f is pure.\n\nBy default, Tuples, NamedTuples, and some other container-like types in Base have children to recurse into. Arrays of numbers do not. To enable recursion into new types, you must provide a method of functor, which can be done using the macro @functor:\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> struct Bar; x; end\n\njulia> @functor Bar\n\njulia> m = Foo(Bar([1,2,3]), (4, 5, Bar(Foo(6, 7))));\n\njulia> fmap(x -> 10x, m)\nFoo(Bar([10, 20, 30]), (40, 50, Bar(Foo(60, 70))))\n\njulia> fmap(string, m)\nFoo(Bar(\"[1, 2, 3]\"), (\"4\", \"5\", Bar(Foo(\"6\", \"7\"))))\n\njulia> fmap(string, m, exclude = v -> v isa Bar)\nFoo(\"Bar([1, 2, 3])\", (4, 5, \"Bar(Foo(6, 7))\"))\n\nTo recurse into custom types without reconstructing them afterwards, use fmapstructure.\n\nFor advanced customization of the traversal behaviour, pass a custom walk function that subtypes Functors.AbstractWalk. The call fmap(f, x, ys...; walk = mywalk) will wrap mywalk in ExcludeWalk then CachedWalk. Here, ExcludeWalk is responsible for applying f at excluded nodes. For a low-level interface for executing a user-constructed walk, see execute.\n\njulia> struct MyWalk <: Functors.AbstractWalk end\n\njulia> (::MyWalk)(recurse, x) = x isa Bar ? \"hello\" :\n Functors.DefaultWalk()(recurse, x)\n\njulia> fmap(x -> 10x, m; walk = MyWalk())\nFoo(\"hello\", (40, 50, \"hello\"))\n\nThe behaviour when the same node appears twice can be altered by giving a value to the prune keyword, which is then used in place of all but the first:\n\njulia> twice = [1, 2];\n\njulia> fmap(float, (x = twice, y = [1,2], z = twice); prune = missing)\n(x = [1.0, 2.0], y = [1.0, 2.0], z = missing)\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.@functor","page":"API","title":"Functors.@functor","text":"@functor T\n@functor T (x,)\n\nAdds methods to functor allowing recursion into objects of type T, and reconstruction. Assumes that T has a constructor accepting all of its fields, which is true unless you have provided an inner constructor which does not.\n\nBy default all fields of T are considered children; this can be restricted be restructed by providing a tuple of field names.\n\nExamples\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> Functors.children(Foo(1,2))\n(x = 1, y = 2)\n\njulia> _, re = Functors.functor(Foo(1,2));\n\njulia> re((10, 20))\nFoo(10, 20)\n\njulia> struct TwoThirds a; b; c; end\n\njulia> @functor TwoThirds (a, c)\n\njulia> ch2, re3 = Functors.functor(TwoThirds(10,20,30));\n\njulia> ch2\n(a = 10, c = 30)\n\njulia> re3((\"ten\", \"thirty\"))\nTwoThirds(\"ten\", 20, \"thirty\")\n\njulia> fmap(x -> 10x, TwoThirds(Foo(1,2), Foo(3,4), 56))\nTwoThirds(Foo(10, 20), Foo(3, 4), 560)\n\n\n\n\n\n","category":"macro"},{"location":"api/#Functors.@leaf","page":"API","title":"Functors.@leaf","text":"@leaf T\n\nDefine functor for the type T so that isleaf(x::T) == true.\n\n\n\n\n\n","category":"macro"},{"location":"api/","page":"API","title":"API","text":"Functors.functor\nFunctors.children\nFunctors.isleaf","category":"page"},{"location":"api/#Functors.functor","page":"API","title":"Functors.functor","text":"Functors.functor(x) = functor(typeof(x), x)\n\nReturns a tuple containing, first, a NamedTuple of the children of x (typically its fields), and second, a reconstruction funciton. This controls the behaviour of fmap.\n\nMethods should be added to functor(::Type{T}, x) for custom types, usually using the macro @functor.\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.children","page":"API","title":"Functors.children","text":"Functors.children(x)\n\nReturn the children of x as defined by functor. Equivalent to functor(x)[1].\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.isleaf","page":"API","title":"Functors.isleaf","text":"Functors.isleaf(x)\n\nReturn true if x has no children according to functor.\n\nExamples\n\njulia> Functors.isleaf(1)\ntrue\n\njulia> Functors.isleaf([2, 3, 4])\ntrue\n\njulia> Functors.isleaf([\"five\", [6, 7]])\nfalse\n\njulia> Functors.isleaf([])\nfalse\n\njulia> Functors.isleaf((8, 9))\nfalse\n\njulia> Functors.isleaf(())\ntrue\n\n\n\n\n\n","category":"function"},{"location":"api/","page":"API","title":"API","text":"Functors.AbstractWalk\nFunctors.execute\nFunctors.DefaultWalk\nFunctors.StructuralWalk\nFunctors.ExcludeWalk\nFunctors.CachedWalk\nFunctors.CollectWalk\nFunctors.AnonymousWalk\nFunctors.IterateWalk","category":"page"},{"location":"api/#Functors.AbstractWalk","page":"API","title":"Functors.AbstractWalk","text":"AbstractWalk\n\nAny walk for use with fmap should inherit from this type. A walk subtyping AbstractWalk must satisfy the walk function interface:\n\nstruct MyWalk <: AbstractWalk end\n\nfunction (::MyWalk)(recurse, x, ys...)\n # implement this\nend\n\nThe 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.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.execute","page":"API","title":"Functors.execute","text":"execute(walk, x, ys...)\n\nExecute a walk that recursively calls itself, starting at a node x in a Functors tree, as well as optional associated nodes ys... in other Functors trees. Any custom walk function that subtypes Functors.AbstractWalk is permitted.\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.DefaultWalk","page":"API","title":"Functors.DefaultWalk","text":"DefaultWalk()\n\nThe default walk behavior for Functors.jl. Walks all the Functors.children of trees (x, ys...) based on the structure of x. The resulting mapped child nodes are restructured into the type of x.\n\nSee fmap for more information.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.StructuralWalk","page":"API","title":"Functors.StructuralWalk","text":"StructuralWalk()\n\nA structural variant of Functors.DefaultWalk. The recursion behavior is identical, but the mapped children are not restructured.\n\nSee fmapstructure for more information.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.ExcludeWalk","page":"API","title":"Functors.ExcludeWalk","text":"ExcludeWalk(walk, fn, exclude)\n\nA 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.\n\nTypically wraps an existing walk for use with fmap.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.CachedWalk","page":"API","title":"Functors.CachedWalk","text":"CachedWalk(walk[; prune])\n\nA 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:\n\nprune is specified, then it is returned, or\nprune is unspecified, and the previously cached recursion of (x, ys...) returned.\n\nTypically wraps an existing walk for use with fmap.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.CollectWalk","page":"API","title":"Functors.CollectWalk","text":"CollectWalk()\n\nA walk that recurses into a node x via Functors.children, storing the recursion history in a cache. The resulting ordered recursion history is returned.\n\nSee fcollect for more information.\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.AnonymousWalk","page":"API","title":"Functors.AnonymousWalk","text":"AnonymousWalk(walk_fn)\n\nWrap a walk_fn so that AnonymousWalk(walk_fn) isa AbstractWalk. This type only exists for backwards compatability and should not be directly used. Attempting to wrap an existing AbstractWalk is a no-op (i.e. it is not wrapped).\n\n\n\n\n\n","category":"type"},{"location":"api/#Functors.IterateWalk","page":"API","title":"Functors.IterateWalk","text":"IterateWalk()\n\nA walk that walks all the Functors.children of trees (x, ys...) and concatenates the iterators of the children via Iterators.flatten. The resulting iterator is returned.\n\nWhen used with fmap, the provided function f should return an iterator. For example, to iterate through the square of every scalar value:\n\njulia> x = ([1, 2, 3], 4, (5, 6, [7, 8]));\n\njulia> make_iterator(x) = x isa AbstractVector ? x.^2 : (x^2,);\n\njulia> iter = fmap(make_iterator, x; walk=Functors.IterateWalk(), cache=nothing);\n\njulia> collect(iter)\n8-element Vector{Int64}:\n 1\n 4\n 9\n 16\n 25\n 36\n 49\n 64\n\nWe can also simultaneously iterate through multiple functors:\n\njulia> y = ([8, 7, 6], 5, (4, 3, [2, 1]));\n\njulia> make_zipped_iterator(x, y) = zip(make_iterator(x), make_iterator(y));\n\njulia> zipped_iter = fmap(make_zipped_iterator, x, y; walk=Functors.IterateWalk(), cache=nothing);\n\njulia> collect(zipped_iter)\n8-element Vector{Tuple{Int64, Int64}}:\n (1, 64)\n (4, 49)\n (9, 36)\n (16, 25)\n (25, 16)\n (36, 9)\n (49, 4)\n (64, 1)\n\n\n\n\n\n","category":"type"},{"location":"api/","page":"API","title":"API","text":"Functors.fmapstructure\nFunctors.fcollect\nFunctors.fleaves","category":"page"},{"location":"api/#Functors.fmapstructure","page":"API","title":"Functors.fmapstructure","text":"fmapstructure(f, x; exclude = isleaf)\n\nLike fmap, but doesn't preserve the type of custom structs. Instead, it returns a NamedTuple (or a Tuple, or an array), or a nested set of these.\n\nUseful for when the output must not contain custom structs.\n\nExamples\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> m = Foo([1,2,3], [4, (5, 6), Foo(7, 8)]);\n\njulia> fmapstructure(x -> 2x, m)\n(x = [2, 4, 6], y = Any[8, (10, 12), (x = 14, y = 16)])\n\njulia> fmapstructure(println, m)\n[1, 2, 3]\n4\n5\n6\n7\n8\n(x = nothing, y = Any[nothing, (nothing, nothing), (x = nothing, y = nothing)])\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.fcollect","page":"API","title":"Functors.fcollect","text":"fcollect(x; exclude = v -> false)\n\nTraverse x by recursing each child of x as defined by functor and collecting the results into a flat array, ordered by a breadth-first traversal of x, respecting the iteration order of children calls.\n\nDoesn't recurse inside branches rooted at nodes v for which exclude(v) == true. In such cases, the root v is also excluded from the result. By default, exclude always yields false.\n\nSee also children.\n\nExamples\n\njulia> struct Foo; x; y; end\n\njulia> @functor Foo\n\njulia> struct Bar; x; end\n\njulia> @functor Bar\n\njulia> struct TypeWithNoChildren; x; y; end\n\njulia> m = Foo(Bar([1,2,3]), TypeWithNoChildren(:a, :b))\nFoo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n\njulia> fcollect(m)\n4-element Vector{Any}:\n Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n Bar([1, 2, 3])\n [1, 2, 3]\n TypeWithNoChildren(:a, :b)\n\njulia> fcollect(m, exclude = v -> v isa Bar)\n2-element Vector{Any}:\n Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n TypeWithNoChildren(:a, :b)\n\njulia> fcollect(m, exclude = v -> Functors.isleaf(v))\n2-element Vector{Any}:\n Foo(Bar([1, 2, 3]), TypeWithNoChildren(:a, :b))\n Bar([1, 2, 3])\n\n\n\n\n\n","category":"function"},{"location":"api/#Functors.fleaves","page":"API","title":"Functors.fleaves","text":"fleaves(x; exclude = isleaf)\n\nTraverse x by recursing each child of x as defined by functor and collecting the leaves into a flat array, ordered by a breadth-first traversal of x, respecting the iteration order of children calls.\n\nThe exclude function is used to determine whether to recurse into a node, therefore identifying the leaves as the nodes for which exclude returns true.\n\nSee also fcollect for a similar function that collects all nodes instead.\n\nExamples\n\n```jldoctest julia> struct Bar; x; end\n\njulia> @functor Bar\n\njulia> struct TypeWithNoChildren; x; y; end\n\njulia> m = (a=Bar([1,2,3]), b=TypeWithNoChildren(4, 5))\n\njulia> fleaves(m) 2-element Vector{Any}: [1, 2, 3] TypeWithNoChildren(:a, :b)\n\n\n\n\n\n","category":"function"},{"location":"#Functors.jl","page":"Home","title":"Functors.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Functors.jl provides a set of tools to represent functors. Functors are a powerful means to apply functions to generic objects without changing their structure.","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"#Basic-Usage-and-Implementation","page":"Home","title":"Basic Usage and Implementation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"When one marks a structure as @functor 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.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The workhorse of fmap is actually a lower level function, functor:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> using Functors\n\njulia> struct Foo\n x\n y\n end\n\njulia> @functor Foo\n\njulia> foo = Foo(1, [1, 2, 3]) # notice all the elements are integers\n\njulia> xs, re = Functors.functor(foo)\n((x = 1, y = [1, 2, 3]), var\"#21#22\"())\n\njulia> re(map(float, xs)) # element types have been switched out for floating point numbers\nFoo(1.0, [1.0, 2.0, 3.0])","category":"page"},{"location":"","page":"Home","title":"Home","text":"functor returns the parts of the object that can be inspected, as well as a reconstruction function (shown as re) that takes those values and restructures them back into an object of the original type.","category":"page"},{"location":"","page":"Home","title":"Home","text":"To include only certain fields of a struct, one can pass a tuple of field names to @functor:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> struct Baz\n x\n y\n end\n\njulia> @functor Baz (x,)\n\njulia> model = Baz(1, 2)\nBaz(1, 2)\n\njulia> fmap(float, model)\nBaz(1.0, 2)","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"#Appropriate-Use","page":"Home","title":"Appropriate Use","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"warning: Not everything should be a functor!\nDue to its generic nature it is very attractive to mark several structures as @functor when it may not be quite safe to do so.","category":"page"},{"location":"","page":"Home","title":"Home","text":"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.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Examples of this include element types of arrays which typically have their own mathematical operations defined. Adding a @functor to such a type would end up missing methods such as +(::MyElementType, ::MyElementType). Think RGB from Colors.jl.","category":"page"}] }