Why Julia returns error on myit .+ 1 in the following code?

I want to learn the broadcasting, and play with the following type, that mimics the UnitRange type:

julia> struct MyArr 
    m_begin::NTuple{3, Int32}
    m_count::NTuple{3, UInt32}
end

julia> myit=MyArr((2,3,4), (3,2,1))

julia> broadcasted(::typeof(+), ma::MyArr, i::Number) = MyAr(ma.m_begin .+ i, ma.m_count)

julia> myit .+ 1
ERROR: MethodError: no method matching length(::MyArr)
Closest candidates are:
  length(::Core.SimpleVector) at essentials.jl:571
  length(::Base.MethodList) at reflection.jl:728
  length(::Core.MethodTable) at reflection.jl:802
  ...
Stacktrace:
 [1] _similar_for(::UnitRange{Int64}, ::Type, ::MyArr, ::Base.HasLength) at ./array.jl:532
 [2] _collect(::UnitRange{Int64}, ::MyArr, ::Base.HasEltype, ::Base.HasLength) at ./array.jl:563
 [3] collect(::MyArr) at ./array.jl:557
 [4] broadcastable(::MyArr) at ./broadcast.jl:609
 [5] broadcasted(::Function, ::MyArr, ::Int64) at ./broadcast.jl:1139
 [6] top-level scope at none:0

I would expect an object MyArr((3,4,5), (3,2,1)), not error.

 Base.Broadcast.broadcasted(::typeof(+), ma::MyArr, i::Number) = MyArr(ma.m_begin .+ i, ma.m_count)

You need to extend the broadcasted function.

1 Like

It appears that you are trying to implement a custom array type. You will probably want to subtype AbstractArray in that case and read the section on implementing array types:

https://docs.julialang.org/en/v1/manual/interfaces

1 Like

Thanks, but it is not my goal - I want to understand how the broadcasting works. That’s why I start with as little help of Base as possible and proceed from that.

If I subtype from an AbstractArray (and implement its informal interface) I will get too much help from the Base Julia library, and lose a sense what can be customized and how.

In particular, I would like to understand how to implement proper broadcasting of arrays held in GPU memory (to help to implement the TensorFlow.jl module).

I also want to learn if (and how) I can implement broadcasting over arrays that are indexed base 0 (like in C++).

Do you know of someone who has both knowledge and time to share knowledge of an (informal) interface of the AbstractRange type family? I can write the documentation in return.

There’s also a section in there on broadcasting specifically:

https://docs.julialang.org/en/v1/manual/interfaces/#man-interfaces-broadcasting-1

There was also a blog post about this back when the new broadcasting infrastructure landed in 0.7:

https://julialang.org/blog/2018/05/extensible-broadcast-fusion

Thank you! I did not know about the last link. I still have some questions, though.

  1. Is BroadcastStyle only a trait, or does it have an interface? Or is it a function? Is Base.BroadcastStyle the constructor for this type, or is it syntactically unrelated function? The manual says “BroadcastStyle is an abstract type and trait-function. (…)”.

  2. What exactly is the difference between materialize and copy[to!]? In the source I can find a call to the instantiate function that inserts axes. Why does it do that? What is the use case that takes advantage of this indirection?

  3. What exactly is the relationship between IndexStyle and BroadcastStyle? Is the only thing that relates them is the default implementation of the copy[to!] (or maybe materialize?) functions?

  4. Do you know about (and is there any possibility to have) any other IndexStyle than IndexLinear and IndexCartesian?

  5. Why do I need to specialize copy when there is a copyto!? Isn’t the copy always inferior w.r.t. performance?

  6. Am I right to assume that the similar(::Broadcasted{MyBroadcastStyle}, ::Type{ElType}) function is only called by the default implementation of the copy(::Broadcasted)? How can I ensure, that only copyto!(dest, ::Broadcasted) is called?

  7. Broadcast style and IndexStyle are related to each other, and so does the AbstractRange type family. Yet these types’ interface is not (yet) documented… and I suspect their implementations do play an important role in the performance of the broadcast mechanism.

Here are my sources of information, so far:

  1. The manual (specifically the chapters about arrays, interfaces and broadcasting)
  2. The talk by Matt Bauman on the JuliaCon 2018 in London (https://www.youtube.com/watch?v=jS9eouMJf_Y(
  3. Matt Bauman’s post in the discourse: How to customize the new broadcasting infrastructure in v0.7
  4. JuliaLang.org blog entry about the broadcast function in 0.7 (https://julialang.org/blog/2018/05/extensible-broadcast-fusion)

I almost never read the source code. I still hope to get a higher level explanation from the devs. :-)…

You are missing a lot then. Base has a lot of great examples.

It is indeed all of the above. It’s an abstract type. As you cannot construct abstract types, its “constructor” function is what you call (and/or implement) to determine which style a given object has. Its “interface” (if you want to call it that) is extremely minimal — if you define your own subtype of BroadcastStyle you need to “hook it up” by implementing that abstract constructor to return your subtype as appropriate.

There are a few reasons for this:

  • materialize provides a stable entry point for the parser. It’s the front end, and you really should not need to override this. It’s a really simple function: it instantiates the Broadcasted and then copy[to!]s it.
  • Now why do we have instantiate? Walking through all the arguments, checking their shapes, and computing the resulting outermost shape can be expensive… and we don’t want to have to pay that cost every time someone calls axes(bc) — that’s a simple function that is typically considered free or cheap. So Broadcasted objects have an optional third field — the cached axes. So why don’t we just always do this in copy[to!]? Well, then it’d mean that every custom implementation would need to deal with this. Further, some implementations just want to opt out of the axis caching behavior because computing the result is indeed cheap! Examples here include tuples and 0-dimensional arrays, both of which have their own style.
  • There’s another reason for instantiate, though: in the case of broadcasted assignment (.=), the LHS of the expression doesn’t appear in the Broadcasted object but it completely determines the resulting axes! So instantiate takes an optional second argument, which is the shape that the Broadcasted object must evaluate to.

It’s completely unrelated. Iterating over eachindex(bc) will always return an index that indexes at the full dimensionality of the resulting shape.

Again, completely unrelated from broadcasting and broadcast styles. I experimented with adding my own index style back around 0.4/0.5 timeframe with the un-registered and un-supported RaggedArrays.jl. You can see what it required back then, but this isn’t something that Julia itself officially supports doing, so doing so will require you to dig into the source code and see what’s required there.

Julia isn’t always able to construct the appropriate resulting container in order to defer to copyto!. Indeed, it can only do that when it can divine out the return type of the broadcasted operation. So you cannot count on copy simply calling similar and then copyto!. That’s why you should implement copy. Unfortunately, this is a hard thing to implement in the type-unstable case because we cannot rely on getting a good answer from inference. We need to incrementally pull out each element, make sure it can fit, and if it doesn’t fit, copy all the incremental progress to a new, wider container, and then keep going.

I don’t fully understand these questions. We don’t (yet) have an officially supported first class way to construct Broadcasteds, so I suppose that for now whatever method copy(::Broadcasted{MyBroadcastStyle}) hits will be the only one to have a chance to call similar, but that might change in the future. But it really shouldn’t matter — you need to return appropriate storage regardless of the caller.

I really don’t think that you need to fully understand IndexStyle and AbstractRange here. Their implementations are not relevant for broadcasting and implementing custom broadcasting.