oschulz / FunctionChains.jl

Function chains in Julia
Other
4 stars 0 forks source link

Add convenience function for repeated functions #13

Open oschulz opened 6 months ago

oschulz commented 6 months ago

Add a convenience function

frepeat(f, n::Integer) = fchain(Iterators.repeated(f, n))

and

∘̂(f, n::Integer) = frepeat(f, n)

Implements the functionality described in JuliaLang/julia#39042 and JuliaMath/InverseFunctions.jl#43.

oschulz commented 6 months ago

@jariji , it it sufficient for you to support positive n? Supporting negative n would require a hard dependency on InverseFunctions and it would result in type instability (except maybe in cases of compiler constant propagation).

oschulz commented 6 months ago

@uniment , I think you had use cases with a negative n in mind, though?

jariji commented 6 months ago

I would be happy to use frepeat with positive n.

Could you explain why negative n would cause type instability? Using the snippet I posted in https://github.com/JuliaMath/InverseFunctions.jl/issues/43 I'm not seeing it:

julia> @report_opt (exp∘̂(2))(ℯ)
No errors detected

julia> @report_opt (exp∘̂(-2))(ℯ)
No errors detected
oschulz commented 6 months ago

Using the snippet I posted in JuliaMath/InverseFunctions.jl#43 I'm not seeing it

I think in your example that is because both exp and log return the same output type for a given input type. But (with your implementation from JuliaMath/InverseFunctions.jl#43):

julia> @inferred (InverseFunctions.square∘̂(2))(3)
ERROR: return type Int64 does not match inferred return type Union{Float64, Int64}

Here, square returns an integer type for an integer input, but sqrt always returns a floating point type, so the "if n is negative use inverse" approach cannot be type stable. The return type simply cannot be inferred at compile time, except (maybe) if n is constant and if the implementation of ∘̂ gets inlined by the compiler.

The same issue occurs with the implementation

@inline function ∘̂(f, n::Integer)
    if n == 0
        identity
    elseif n > 0
        frepeat(f, n)
    else
        frepeat(inverse(f), -n)
    end
end

It can be type-stable when the compiler can propagate a constant n and inline and elide the conditional code, but it's type-unstable in general. That happens more often in this case, because the decision on which path to take is made earlier (so higher chance of constant propagation), but it's still not type-stable in general.

oschulz commented 6 months ago

That being said, I'm Ok with allowing for negative n in ∘̂ if people have use cases and aren't too worried about the type stability issue (frepeat, on the other hand, should definitely require a non-negative n).

jariji commented 1 month ago

Is it fair to support the n=0 case as the identity function or does that go wrong somewhere?

oschulz commented 1 month ago

Is it fair to support the n=0 case as the identity function or does that go wrong somewhere?

Yes, n = 0 would be the identity.