Exponentiation operator for iterated functions?

Would it make sense to define the ^ operator for iterated functions? Something like:

^(f::Function, n::Integer) = ∘(fill(f,n)...)

Then we can do things like

typeof(1.0) |> supertype^3

Instead of

typeof(1.0) |> supertype |> supertype |> supertype

A possible downside: people might expect (sin^2)(x) to mean sin(x)^2 instead of sin(sin(x)) (although they should not IMO :wink:).

1 Like

I think not. Whatever works for functions should work for all callables, and some types can be callables and at the same time define methods for ^ (eg as an operator on some algebra).

The basic issue is that ^ in Julia is for iteration of *. So it would be inconsistent (and poorly composable as @Tamas_Papp notes) to overload it for iteration of .

2 Likes

Ah right it makes sense. Maybe at some point another operator will prove adequate…

In case you’re not aware, you can do it for your own functions if you want!

julia> supertype(T) = Base.supertype(T)
supertype (generic function with 1 method)

julia> Base.:^(::typeof(supertype), n::Integer) = n == 1 ? supertype : supertype ∘ supertype^(n-1)

julia> (supertype^3)(Float64)
Number

Thanks, my original post actually included a working implementation, but it’s nice to see another one using recursion. It makes me realize that my version only works for n>=2.

The difference is that your original version commits type piracy if it’s not in Base because it applies to all Function. That’s (one of) the reason(s) why you were suggesting its inclusion, I thought.

Mine applies only to the function that I own. That’s also the reason why I created a new function (with the same name) in the first place.

Does that make sense? I should have made the distinction clearer.

1 Like

Ah sorry I read your message too fast and missed the point, it makes perfect sense.

1 Like

What about something like this?

function Base.:^(x::Any, exponent::Tuple{Function,Integer})
    op, p = exponent
    p == 1 ? x : reduce(op, fill(x, p))
end
julia> typeof(1.0) |> supertype^(∘,3)
Number

julia> 3^(+,3) # Iterated addition -> Scalar multiplication
9

julia> 3^(^, 3) # Tetration 😄
19683

Implementation and syntax is obviously up for debate: For example, closure could work too e.g. 3 ^(+) 3, but I couldn’t figure out how to do it; I don’t know how to return an anonymous function that behaves like an operator.

I feel like a more general exponentiation operator has several benefits:

  • Convenient and intuitive iteration of any closed binary operator (e.g. the main post) without having to implement a new ^ method for custom types
  • When you have more than one product-like operation defined, you can eliminate ambiguity as to which operation your exponentiation is referring to.
  • If you want to be more strict, instead of overloading ^ for custom types to mean iteration of some non-* operator, you can instead specify exactly what you mean without much loss in convenience.
  • Code golf :stuck_out_tongue:

As for how useful it would be in practice…I’m not the person to ask.


My dream is for regular exponentiation x^p to just be a special case via

(^)(x::Any, p::Integer) = x^(*, p)

:ghost:

See related this thread I made.

I don’t think this is a very common use case for the typical Julia user.

It may be relevant algebra and similar, but it’s perfectly fine to have a package define some syntax for this (without type piracy, of course), perhaps using one of the available operators.

1 Like

Interesting generalization… It’s basically providing a shorter reduce syntax for the case where the collection is a repetition of the same element. Then I would rather provide nice syntax for the collection itself:

julia> ↑(el,n::Integer) = fill(el,n)

julia> reduce(*, 2↑10)
1024

julia> typeof(1.0) |> ∘(supertype↑3...)
Number

We could also define a reciprocal operator for the reduction:

↓(itr, op::Function) = reduce(op, itr)

julia> (2↑10)↓*
1024

julia> typeof(1.0) |> (supertype↑3)↓∘
Number

But the definition of should probably define the associativity of reduction (using foldl or foldr), and it’s not clear which it should be.

In any case I think @Tamas_Papp is right, it’s better to define this in a package using another operator.

2 Likes