From 5f9472824486c17bc8bf035cfe7ca31d2c8bfbe4 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Wed, 30 Dec 2020 10:37:02 -0500 Subject: [PATCH 1/2] function^n for iterated functions --- base/operators.jl | 62 +++++++++++++++++++++++++++++++++++++++++------ base/show.jl | 1 + test/operators.jl | 29 ++++++++++++++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index a35c44276a2e4..37c2f7c55f985 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -559,7 +559,7 @@ extrema(f, x::Real) = (y = f(x); (y, y)) """ identity(x) -The identity function. Returns its argument. +The identity function. Returns its argument. Any keyword arguments are ignored. # Examples ```jldoctest @@ -567,7 +567,7 @@ julia> identity("Well, what did you expect?") "Well, what did you expect?" ``` """ -identity(x) = x +identity(x; kws...) = x +(x::Number) = x *(x::Number) = x @@ -931,7 +931,7 @@ julia> fs = [ julia> ∘(fs...)(3) 3.0 ``` -See also [`ComposedFunction`](@ref). +See also [`ComposedFunction`](@ref) and [`IteratedFunction`](@ref). """ function ∘ end @@ -940,9 +940,9 @@ function ∘ end Represents the composition of two callable objects `outer::Outer` and `inner::Inner`. That is ```julia -ComposedFunction(outer, inner)(args...; kw...) === outer(inner(args...; kw...)) +ComposedFunction(outer, inner)(args...; kw...) === outer(inner(args...; kw...); kw...) ``` -The preferred way to construct instance of `ComposedFunction` is to use the composition operator [`∘`](@ref): +The preferred way to construct an instance of `ComposedFunction` is to use the composition operator [`∘`](@ref): ```jldoctest julia> sin ∘ cos === ComposedFunction(sin, cos) true @@ -962,7 +962,7 @@ julia> composition.inner === cos true ``` !!! compat "Julia 1.6" - ComposedFunction requires at least Julia 1.6. In earlier versions `∘` returns an anonymous function instead. + `ComposedFunction` requires at least Julia 1.6. In earlier versions `∘` returns an anonymous function instead. See also [`∘`](@ref). """ @@ -973,7 +973,7 @@ struct ComposedFunction{O,I} <: Function ComposedFunction(outer, inner) = new{Core.Typeof(outer),Core.Typeof(inner)}(outer, inner) end -(c::ComposedFunction)(x...) = c.outer(c.inner(x...)) +(c::ComposedFunction)(x...; kws...) = c.outer(c.inner(x...; kws...); kws...) ∘(f) = f ∘(f, g) = ComposedFunction(f, g) @@ -985,6 +985,54 @@ function show(io::IO, c::ComposedFunction) show(io, c.inner) end +""" + IteratedFunction{F} <: Function + +`IteratedFunction(f,n)` represents the function `f` iterated `n ≥ 0` times on +its input, which must be a single argument. + +That is, for functions `f(x)`, it represents the function +`x -> f(f(f(f(...(f(x))))))` iterated `n` times`. Any keyword arguments +are passed through to all calls. + +If `f isa Function`, you should normally use the construction `f^n` +to form an `IteratedFunction`. If `n` is a literal integer, `f^n` +may construct a more specialized object, e.g. `f^1 == f`, `f^0 == identity`, +and `f^2 == f ∘ f`. + +!!! compat "Julia 1.7" + `IteratedFunction` and `f^n` require at least Julia 1.7. + +See also [`∘`](@ref) and [`ComposedFunction`](@ref). +""" +struct IteratedFunction{F} <: Function + f::F + n::Int + IteratedFunction{F}(f, n::Integer) where {F} = new{F}(f, _check_nonnegative(n)) + IteratedFunction(f, n::Integer) = new{Core.Typeof(f)}(f, _check_nonnegative(n)) +end +_check_nonnegative(n::Integer) = n ≥ 0 ? n : throw(ArgumentError("$n is not ≥ 0")) +^(f::Function, n::Integer) = IteratedFunction(f, n) +^(fn::IteratedFunction, n::Integer) = IteratedFunction(fn.f, fn.n * n) +literal_pow(::typeof(^), f::Function, ::Val{0}) = identity +literal_pow(::typeof(^), f::Function, ::Val{1}) = f +literal_pow(::typeof(^), f::Function, ::Val{2}) = f ∘ f +literal_pow(::typeof(^), f::Function, ::Val{3}) = f ∘ f ∘ f +literal_pow(::typeof(^), f::Function, ::Val{4}) = f ∘ f ∘ f ∘ f +literal_pow(::typeof(^), fn::IteratedFunction, ::Val{2}) = IteratedFunction(fn.f, fn.n * 2) +literal_pow(::typeof(^), fn::IteratedFunction, ::Val{3}) = IteratedFunction(fn.f, fn.n * 3) +literal_pow(::typeof(^), fn::IteratedFunction, ::Val{4}) = IteratedFunction(fn.f, fn.n * 4) +function (fn::IteratedFunction)(x; kws...) + for i in Base.OneTo(fn.n) + x = fn.f(x; kws...) + end + return x +end +function show(io::IO, fn::IteratedFunction) + show(io, fn.f) + print(io, "^", fn.n) +end + """ !f::Function diff --git a/base/show.jl b/base/show.jl index 6a53b1c193f3e..900715cca55fd 100644 --- a/base/show.jl +++ b/base/show.jl @@ -45,6 +45,7 @@ function show(io::IO, ::MIME"text/plain", f::Function) end show(io::IO, ::MIME"text/plain", c::ComposedFunction) = show(io, c) +show(io::IO, ::MIME"text/plain", c::IteratedFunction) = show(io, c) function show(io::IO, ::MIME"text/plain", iter::Union{KeySet,ValueIterator}) isempty(iter) && get(io, :compact, false) && return show(io, iter) diff --git a/test/operators.jl b/test/operators.jl index c14858657ce3b..f14cc95cb3284 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -162,6 +162,35 @@ Base.promote_rule(::Type{T19714}, ::Type{Int}) = T19714 @test sprint(show, "text/plain", uppercase ∘ first) == "uppercase ∘ first" end +@testset "function iteration" begin + @test (sin^0)(1.2) == 1.2 + @test (sin^1)(1.2) == sin(1.2) + @test (sin^2)(1.2) == sin(sin(1.2)) + @test (sin^3)(1.2) == sin(sin(sin(1.2))) + @test (sin^4)(1.2) == sin(sin(sin(sin(1.2)))) + @test (sin^5)(1.2) == sin(sin(sin(sin(sin(1.2))))) + @test sin^0 == identity + @test sin^1 == sin + @test sin^2 == sin ∘ sin + @test repr(sin^8) == "sin^8" == repr("text/plain", sin^8) + @test (sin^8)^2 == sin^16 + @test (sin^8)^10 == sin^80 + let f(x; y) = x + y + @test (f^0)(0; y=3) == 0 + @test (f^1)(0; y=3) == 3 + @test (f^2)(0; y=3) == 3*2 + @test (f^3)(0; y=3) == 3*3 + @test (f^4)(0; y=3) == 3*4 + for n = 0:10 + @test (f^n)(0; y=3) == 3*n + end + end + @test_throws MethodError sin^-1 # calls inv(sin) + let n=-1 + @test_throws ArgumentError sin^n + end +end + @testset "function negation" begin str = randstring(20) @test filter(!isuppercase, str) == replace(str, r"[A-Z]" => "") From b2d3e9ecf525fe8fc388be67a3657d2da817bbde Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Wed, 30 Dec 2020 11:04:13 -0500 Subject: [PATCH 2/2] add IteratedFunction to exports, like ComposedFunction --- base/exports.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/exports.jl b/base/exports.jl index 702c1bf485c3b..6c63e40d61839 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -57,6 +57,7 @@ export IOStream, LinRange, Irrational, + IteratedFunction, Matrix, MergeSort, Missing,