The broadcast interface is involved and complicated, and it is admittedly challenging to implement fully. There will be a number of issues with an incomplete implementation such as yours. At first order, this fails because ^ with a literal lowers to Base.literal_pow with three arguments, not two — see the fifth entry in the stacktrace here:
julia> Bob() .^ 2 # breaks
ERROR: MethodError: no method matching length(::Bob)
Closest candidates are:
length(::ExponentialBackOff)
@ Base error.jl:267
length(::Core.SimpleVector)
@ Base essentials.jl:770
length(::BitSet)
@ Base bitset.jl:348
...
Stacktrace:
[1] _similar_shape(itr::Bob, ::Base.HasLength)
@ Base ./array.jl:710
[2] _collect(cont::UnitRange{Int64}, itr::Bob, ::Base.HasEltype, isz::Base.HasLength)
@ Base ./array.jl:765
[3] collect(itr::Bob)
@ Base ./array.jl:759
[4] broadcastable(x::Bob)
@ Base.Broadcast ./broadcast.jl:743
[5] broadcasted(f::typeof(Base.literal_pow), arg1::Function, arg2::Bob, args::Val{2})
@ Base.Broadcast ./broadcast.jl:1345
[6] top-level scope
@ REPL[27]:1
That’s fairly straightforward to address, but there will likely be other challenges, not least of which are the number of ambiguities your definitions here incur.
broadcasted(f::typeof(Base.literal_pow), arg1::Function, iv::MyType, arg::Val{V}) where V = iv .^ typeof(V)(V)
forced dispatch back to the three-argument broadcasted method and so solved my problem. I’m guessing the literal_pow broadcast exist to enable lazy evaluation of certain things?