Skip to content

Commit 3e00276

Browse files
rebase
2 parents 13348e8 + 7451617 commit 3e00276

File tree

12 files changed

+327
-22
lines changed

12 files changed

+327
-22
lines changed

.github/workflows/TagBot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ jobs:
1414
- uses: JuliaRegistries/TagBot@v1
1515
with:
1616
token: ${{ secrets.GITHUB_TOKEN }}
17+
ssh: ${{ secrets.DOCUMENTER_KEY }}

.github/workflows/ci.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
name: CI
2-
# env:
3-
# JULIA_NUM_THREADS: 2
2+
43
on:
54
pull_request:
65
branches:
@@ -9,6 +8,7 @@ on:
98
branches:
109
- master
1110
tags: '*'
11+
1212
jobs:
1313
test:
1414
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
@@ -57,3 +57,27 @@ jobs:
5757
- uses: julia-actions/julia-runtest@v1
5858
continue-on-error: ${{ matrix.julia-version == 'nightly' }}
5959

60+
docs:
61+
name: Documentation
62+
runs-on: ubuntu-latest
63+
steps:
64+
- uses: actions/checkout@v2
65+
- uses: julia-actions/setup-julia@v1
66+
with:
67+
version: '1.6'
68+
- run: |
69+
julia --project=docs -e '
70+
using Pkg
71+
Pkg.develop(PackageSpec(path=pwd()))
72+
Pkg.instantiate()'
73+
- run: |
74+
julia --project=docs/ -e '
75+
using Functors
76+
using Documenter
77+
using Documenter: doctest
78+
DocMeta.setdocmeta!(Functors, :DocTestSetup, :(using Functors); recursive = true)
79+
doctest(Functors)'
80+
- run: julia --project=docs docs/make.jl
81+
env:
82+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83+
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Functors"
22
uuid = "d9f16b24-f501-4c13-a1f2-28368ffc5196"
33
authors = ["Mike J Innes <mike.j.innes@gmail.com>"]
4-
version = "0.2.1"
4+
version = "0.2.3"
55

66
[deps]
77
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1-
# Functors
1+
# Functors.jl
22

3-
Functors.jl provides a mechanism – really more of a design pattern – for dealing with large structures containing numerical parameters, as in machine learning and optimisation. For large models it can be cumbersome or inefficient to work with parameters as one big, flat vector, and structs help manage complexity; but you also want to easily operate over all parameters at once, e.g. for changing precision or applying an optimiser update step.
3+
[![][docs-stable-img]][docs-stable-url]
4+
[![][docs-dev-img]][docs-dev-url]
5+
[![][action-img]][action-url]
6+
7+
[docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg
8+
[docs-stable-url]: https://fluxml.ai/Functors.jl/stable/
9+
10+
[docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg
11+
[docs-dev-url]: https://fluxml.ai/Functors.jl/dev/
12+
13+
[action-img]: https://github.com/FluxML/Functors.jl/workflows/CI/badge.svg
14+
[action-url]: https://github.com/FluxML/Functors.jl/actions
15+
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.
417

518
Functors.jl provides `fmap` to make those things easy, acting as a 'map over parameters':
619

7-
```julia
20+
```julia-repl
821
julia> using Functors
922
1023
julia> struct Foo
@@ -23,7 +36,7 @@ Foo(1.0, [1.0, 2.0, 3.0])
2336

2437
It works also with deeply-nested models:
2538

26-
```julia
39+
```julia-repl
2740
julia> struct Bar
2841
x
2942
end
@@ -39,7 +52,7 @@ Bar(Foo(1.0, [1.0, 2.0, 3.0]))
3952

4053
The workhorse of `fmap` is actually a lower level function, `functor`:
4154

42-
```julia
55+
```julia-repl
4356
julia> xs, re = functor(Foo(1, [1, 2, 3]))
4457
((x = 1, y = [1, 2, 3]), var"#21#22"())
4558
@@ -51,7 +64,7 @@ Foo(1.0, [1.0, 2.0, 3.0])
5164

5265
To include only certain fields, pass a tuple of field names to `@functor`:
5366

54-
```julia
67+
```julia-repl
5568
julia> struct Baz
5669
x
5770
y
@@ -73,7 +86,8 @@ It is also possible to implement `functor` by hand when greater flexibility is r
7386
For a discussion regarding the need for a `cache` in the implementation of `fmap`, see [here](https://github.com/FluxML/Functors.jl/issues/2).
7487

7588
Use `exclude` for more fine-grained control over whether `fmap` descends into a particular value (the default is `exclude = Functors.isleaf`):
76-
```julia
89+
90+
```julia-repl
7791
julia> using CUDA
7892
7993
julia> x = ['a', 'b', 'c'];

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[deps]
2+
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

docs/make.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Documenter, Functors
2+
3+
DocMeta.setdocmeta!(Functors, :DocTestSetup, :(using Functors); recursive = true)
4+
5+
makedocs(modules = [Functors],
6+
doctest = VERSION == v"1.6",
7+
sitename = "Functors.jl",
8+
pages = ["Home" => "index.md",
9+
"API" => "api.md"],
10+
format = Documenter.HTML(
11+
analytics = "UA-36890222-9",
12+
assets = ["assets/flux.css"],
13+
prettyurls = get(ENV, "CI", nothing) == "true"),
14+
)
15+
16+
deploydocs(repo = "github.com/FluxML/Functors.jl.git",
17+
target = "build",
18+
push_preview = true)

docs/src/api.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
```@docs
2+
Functors.@functor
3+
Functors.fmap
4+
Functors.children
5+
Functors.isleaf
6+
Functors.fcollect
7+
Functors.fmapstructure
8+
```

docs/src/assets/flux.css

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
@import url('https://fonts.googleapis.com/css?family=Lato:400,400i');
2+
3+
body {
4+
font-family: Lato, "Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
5+
}
6+
7+
nav.toc {
8+
padding-top: 0;
9+
background: rgb(240, 240, 240);
10+
line-height: 2em;
11+
cursor: default;
12+
user-select: none;
13+
}
14+
15+
h1+h2 {
16+
margin-top: 0;
17+
}
18+
19+
/* Green banner in ToC */
20+
nav.toc > h1 {
21+
margin-top: 0;
22+
padding-top: 0.4em;
23+
padding-bottom: 0.5em;
24+
border-bottom: 5px solid white;
25+
box-shadow: 0px -2px 5px rgb(60,60,60);
26+
margin-bottom: 0.5em;
27+
background: rgb(60, 150, 60);
28+
29+
font-style: italic;
30+
font-weight: normal;
31+
font-size: 50pt;
32+
text-transform: lowercase;
33+
text-shadow: 2px 2px 5px rgba(0,0,0,0.2);
34+
color: white;
35+
}
36+
37+
/* Reduce ToC font size */
38+
.toctext {
39+
font-size: 10pt;
40+
}
41+
42+
/* Fade out non-clickable ToC headers */
43+
nav.toc ul span.toctext {
44+
color: rgb(180, 180, 180);
45+
}
46+
47+
nav.toc ul .toctext {
48+
color: rgb(100, 100, 100);
49+
}
50+
51+
nav.toc ul a.toctext:hover {
52+
color: inherit;
53+
background: rgb(220, 220, 220);
54+
cursor: default;
55+
}
56+
57+
nav.toc li.current > .toctext {
58+
background: linear-gradient(90deg, rgb(245,245,245) 0%, white 90%);
59+
font-weight: normal;
60+
}
61+
62+
nav.toc ul.internal li.toplevel {
63+
font-weight: normal;
64+
}
65+
66+
/* Content */
67+
68+
article { max-width: none; }
69+
70+
article > p, article > ul {
71+
max-width: 45em;
72+
}
73+
74+
/* Links */
75+
a, a:visited { color: rgb(0, 120, 0); }
76+
article p a { border-bottom: 1px solid rgb(200, 230, 200); }
77+
a:hover, a:visited:hover { color: rgb(0, 80, 0); }
78+
79+
/* Article Links */
80+
article p a { border-bottom: 1px solid rgb(200, 230, 200); }
81+
article p a:hover, article a:visited:hover { color: rgb(0, 120, 0); }
82+
article p a:hover { border-bottom: 1px solid rgb(150, 200, 150); }
83+
84+
/* Doctstrings */
85+
article section.docstring {
86+
padding: 0.5em 0;
87+
border-left: none;
88+
border-right: none;
89+
border-bottom: none;
90+
}
91+
92+
/* Code */
93+
94+
article pre, article p > code {
95+
background: rgb(245, 250, 245);
96+
}
97+
98+
article pre {
99+
border: none;
100+
max-width: none;
101+
padding: 1em;
102+
border-radius: 10px 0px 0px 10px;
103+
margin-left: -1em;
104+
margin-right: -2em;
105+
}
106+
107+
.hljs-comment {
108+
font-style: italic;
109+
}
110+
111+
.hljs-number {
112+
color: rgb(0, 150, 150);
113+
}

docs/src/index.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Functors.jl
2+
3+
Functors.jl provides a set of tools to represent [functors](https://en.wikipedia.org/wiki/Functor_(functional_programming)). Functors are a powerful means to apply functions to generic objects without changing their structure.
4+
5+
Functors can be used in a variety of ways. One is to traverse a complicated or nested structure as a tree and apply a function `f` to every field it encounters along the way.
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.
8+
9+
## Appropriate Use
10+
11+
!!! warning "Not everything should be a functor!"
12+
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.
13+
14+
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.
15+
16+
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.
17+
18+
## Basic Usage and Implementation
19+
20+
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).
21+
22+
The workhorse of fmap is actually a lower level function, functor:
23+
24+
```julia-repl
25+
julia> using Functors
26+
27+
julia> struct Foo
28+
x
29+
y
30+
end
31+
32+
julia> @functor Foo
33+
34+
julia> foo = Foo(1, [1, 2, 3]) # notice all the elements are integers
35+
36+
julia> xs, re = Functors.functor(foo)
37+
((x = 1, y = [1, 2, 3]), var"#21#22"())
38+
39+
julia> re(map(float, xs)) # element types have been switched out for floating point numbers
40+
Foo(1.0, [1.0, 2.0, 3.0])
41+
```
42+
43+
`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.
44+
45+
To include only certain fields of a struct, one can pass a tuple of field names to [`@functor`](@ref):
46+
47+
```julia-repl
48+
julia> struct Baz
49+
x
50+
y
51+
end
52+
53+
julia> @functor Baz (x,)
54+
55+
julia> model = Baz(1, 2)
56+
Baz(1, 2)
57+
58+
julia> fmap(float, model)
59+
Baz(1.0, 2)
60+
```
61+
62+
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.
63+

src/Functors.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module Functors
22

33
using MacroTools
44

5-
export @functor, fmap, fcollect
5+
export @functor, fmap, fmapstructure, fcollect
66

77
include("functor.jl")
88

0 commit comments

Comments
 (0)