Skip to content
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

Expose ComposedFunction as a public API #37517

Merged
merged 15 commits into from
Sep 24, 2020
Merged
28 changes: 28 additions & 0 deletions base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -871,9 +871,37 @@ julia> fs = [
julia> ∘(fs...)(3)
3.0
```
See also [Base.ComposedFunction](@ref).
tkf marked this conversation as resolved.
Show resolved Hide resolved
"""
function ∘ end

"""
ComposedFunction{F,G} <: Function
jw3126 marked this conversation as resolved.
Show resolved Hide resolved

Represents the composition of two callable objects `f::F` and `g::G`.
```jldoctest
julia> sin ∘ cos === Base.ComposedFunction(sin, cos)
true

julia> typeof(sin∘cos)
Base.ComposedFunction{typeof(sin), typeof(cos)}
```
The composed pieces are stored in the fields of `ComposedFunction` and can be retrieved as follows:
```jldoctest
julia> composition = sin ∘ cos
sin ∘ cos

julia> composition.f === sin
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would actually prefer to name the fields to e.g. outer, inner instead of f,g. It is hard to remember if this models f∘g or g∘f.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat neutral about the choice of the field names. I see f and g as something like x and y. It's technically arbitrary but I've seen it many times so I don't think it's super hard to guess the ordering.

Just as a note, one (rather weak) upside of keep using f and g is that we can define ComposedFunction for older julia versions as done in DataFrames.jl (JuliaData/DataFrames.jl#2274 (comment)). However, it doesn't matter much since 1.6 soon will be the LTS (and we can even define getproperty for the closure in Compat.jl).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the order is not easy to guess at all. Just have a look at the wikipedia article. There the order g∘f is more dominant, which is opposed to the current order we use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My personal first guess was btw g∘f.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I sometimes get inner/outer opposite somehow (though this is probably just me). Since you can always flip the arrows (and which direction feels natural depends on the context), I'm not sure if there are truly unambiguous names for them. But I'm likely nitpicking. I agree inner/outer is more descriptive for function composition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With outer, inner it is at least clear that c::ComposedFunction talks about c.outer(c.inner(x)). With f,g it is unclear whether we are talking about c.f(c.g(x)) or c.g(c.f(x)). Remembering the direction of arrows is an independent problem that comes up in both scenarios.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I also get inner outer wrong sometimes, but I can usually recover by pure reasoning without having to dig up the source.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's easier to remember and be confident which function is applied first with inner/outer. I think I'm in the team inner/outer now. How about renaming them in this PR and see the reactions of other reviewers?

Copy link
Contributor

@cmcaine cmcaine Sep 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the little it is worth, I think f and g are fine and their exact interpretation can be clarified in the docstring. That way there doesn't need to be any work on Compat.jl or in other packages.

If you stick with inner and outer, then I think line 881 needs to change:

Represents the composition of two callable objects `f::Outer` and `g::Inner`.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, f and g are confusing, we should not force people to look up the docstring all the time. Also @tkf has a PR to compat
JuliaLang/Compat.jl#720 and supporting f and g is the easy part of that code.

true

julia> composition.g === cos
true
```
!!! compat "Julia 1.6"
ComposedFunction requires at least Julia 1.6. In earlier version `∘` returns an anonymous function instead.

See also [`∘`](@ref).
"""
struct ComposedFunction{F,G} <: Function
f::F
g::G
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ Base.invokelatest
new
Base.:(|>)
Base.:(∘)
Base.ComposedFunction
```

## Syntax
Expand Down