Custom Array type

398 views
Skip to first unread message

Marcus Appelros

unread,
Apr 25, 2015, 7:50:52 AM4/25/15
to julia...@googlegroups.com
Consider the definitions:

type Cubes<:AbstractArray
end
sum(cubes::Cubes)=sum(convert(Array,cubes).^3)

Is it possible to make this work? To create a custom array that has indexing and all at inception? Looked up the definition of Array and it is in the commented section on types implemented in C, its constructor is a ccall, if it isn't possible in pure julia would it be possible with C-code that doesn't prompt a rebuild?

Tim Holy

unread,
Apr 25, 2015, 10:02:56 AM4/25/15
to julia...@googlegroups.com
You want to store the array object inside Cubes.

type Cubes{T,N} <: AbstractArray{T,N}
data::Array{T,N}
end
sum(cubes::Cubes) = sum(cubes.data.^3)

--Tim

Marcus Appelros

unread,
Apr 25, 2015, 12:40:08 PM4/25/15
to julia...@googlegroups.com
That gives the following error on 0.3 and 0.4:

julia> type Cubes<:AbstractArray;data;end

julia> c=Cubes([1,2,3])
Error showing value of type Cubes:
ERROR: MethodError: `size` has no method matching size(::Cubes)
Closest candidates are:
  size(::Any, ::Integer, ::Integer, ::Integer...)
 in showarray at show.jl:1162
 in anonymous at replutil.jl:27
 in with_output_limit at ./show.jl:1202
 in writemime at replutil.jl:26
 in display at REPL.jl:111
 in display at REPL.jl:114
 in display at multimedia.jl:149
 in print_response at REPL.jl:133
 in print_response at REPL.jl:118
 in anonymous at REPL.jl:595
 in run_interface at ./LineEdit.jl:1552
 in run_frontend at ./REPL.jl:835
 in run_repl at ./REPL.jl:164
 in _start at ./client.jl:430

0.3:
julia> type Cubes{T,N} <: AbstractArray{T,N}
           data::Array{T,N}
       end

julia> Cubes([1,2,3])
Error showing value of type Cubes{Int64,1}:
ERROR: `size` has no method matching size(::Cubes{Int64,1})
 in writemime at replutil.jl:18
 in display at REPL.jl:117
 in display at REPL.jl:120
 in display at multimedia.jl:149
 in print_response at REPL.jl:139
 in print_response at REPL.jl:124
 in anonymous at REPL.jl:586
 in run_interface at ./LineEdit.jl:1379
 in run_frontend at ./REPL.jl:818
 in run_repl at ./REPL.jl:169
 in _start at ./client.jl:400

Marcus Appelros

unread,
Apr 25, 2015, 12:44:09 PM4/25/15
to julia...@googlegroups.com
If size is defined the result is possibly stranger:


julia> type Cubes{T,N} <: AbstractArray{T,N}
           data::Array{T,N}
       end

julia> Base.size(c::Cubes)=size(c.data)
size (generic function with 52 methods)

julia> Cubes([1,2,3])
3-element Cubes{Int64,1}:
 #undef
 #undef
 #undef

Identical on 0.3 and 0.4.

Simon Danisch

unread,
Apr 25, 2015, 12:50:20 PM4/25/15
to julia...@googlegroups.com
You need to implement the minimal array interface.
Something along these lines:

length(c::Cube) = length(c.data)
eltype{T,N}(c::Cube{T,N}) = T
ndims{T,N}(c::Cube{T,N}) = N
size(c::Cube) = size(c.data) 
size((c::Cube, i::Integer) = size(c.data, i) 

Simon Danisch

unread,
Apr 25, 2015, 12:52:16 PM4/25/15
to julia...@googlegroups.com
Some explanation:
Now that Julia thinks that Cube is an array, it assumes that these functions are defined.
So when you call show, it will try to display it as an array.
By the way I forgot the most important one:
getindex(c::Cube, i...) = c.data[i...]
setindex!(c::Cube, value, i...) = c.data[i...] = value

Am Samstag, 25. April 2015 13:50:52 UTC+2 schrieb Marcus Appelros:

Marcus Appelros

unread,
Apr 25, 2015, 1:05:04 PM4/25/15
to julia...@googlegroups.com
Which is exactly what should be possible to avoid, if we anyhow have to define all the functions what is the meaning in descending from AbstractArray? The usefulness of having an abstract Component is to make concrete instances that retain all the abstract functionality.

Simon Danisch

unread,
Apr 25, 2015, 1:22:54 PM4/25/15
to julia...@googlegroups.com
Well you're right there, but inheriting from abstract array can't do magic.
What you'd be asking for is, that inheriting from AbstractArray does some magic which can deal with any thinkable array type people can come up with.
On the other hand, just defining 4-5 functions is not that much and you can then get quite a few functions for free than.
Something what I've been wanting though is, that if the type is really pretty much the same just with another name, it should  be possible to inherit all the functions.
I opened an issue some time ago about this:

As you can see, it's quite tricky to solve this cleanly.


Am Samstag, 25. April 2015 13:50:52 UTC+2 schrieb Marcus Appelros:

Patrick O'Leary

unread,
Apr 25, 2015, 1:24:35 PM4/25/15
to julia...@googlegroups.com
On Saturday, April 25, 2015 at 12:05:04 PM UTC-5, Marcus Appelros wrote:
Which is exactly what should be possible to avoid, if we anyhow have to define all the functions what is the meaning in descending from AbstractArray? The usefulness of having an abstract Component is to make concrete instances that retain all the abstract functionality.

There is no generic way to implement all these methods, though. In a traditional object-oriented environment, these would be abstract methods--define these and then you can get more things for free.

There is a generic implementation of `length()` you can get if you define `size()`:

length(t::AbstractArray) = prod(size(t))::Int

I believe you get `eltype()` and `ndims()` for free, too, looking at the definitions in abstractarray.jl.

Patrick O'Leary

unread,
Apr 25, 2015, 1:32:12 PM4/25/15
to julia...@googlegroups.com
On Saturday, April 25, 2015 at 11:44:09 AM UTC-5, Marcus Appelros wrote:
julia> Cubes([1,2,3])
3-element Cubes{Int64,1}:
 #undef
 #undef
 #undef

show() is trying to get the values in the array, but doesn't know that it can get them from Cubes.data. What you really want to be able to do is delegate everything to the .data member, but there's no convenient way to do that (there is also no convenient way to do that in C++, so it's not a new problem.)

Defining getindex() should be enough to get show() working. As Simon mentions, it would be good to define setindex!() as well so you can mutate the underlying array.

Marcus Appelros

unread,
Apr 25, 2015, 1:55:46 PM4/25/15
to julia...@googlegroups.com
Feels somehow sufficient to direct all functions to the data field. We can have a macro like

@foranyfunction f(c::Cubes,a::AnyArgs)=f(c.data,a)


"What you really want to be able to do is delegate everything to the .data member, but there's no convenient way to do that"
There are some existing macros that take a list of functions and define them on a type, we can wrap a macro that acts on all functions in methods(T).

Or allow inheriting from concrete types.

Or allow specifying abstract types like AbstractArray{T,N}.

Mauro

unread,
Apr 25, 2015, 1:59:05 PM4/25/15
to julia...@googlegroups.com
On Sat, 2015-04-25 at 19:55, Marcus Appelros <marcus....@gmail.com> wrote:
> Feels somehow sufficient to direct all functions to the data field. We can
> have a macro like
>
> @foranyfunction f(c::Cubes,a::AnyArgs)=f(c.data,a)

https://github.com/JuliaLang/julia/pull/3292

Marcus Appelros

unread,
Apr 25, 2015, 2:49:55 PM4/25/15
to julia...@googlegroups.com
"https://github.com/JuliaLang/julia/pull/3292"
Interesting, has it been implemented now?

Maybe there is a more efficient method, since we are getting further
away from the definition of Array by adding a data field, the
commented source version is empty as in the OP, so when the
constructor calls C it is storing the identifier "Array" somewhere. We
can create a identical constructor with the difference that it accepts
a keyword for what to write in place of Array.

Mauro

unread,
Apr 25, 2015, 3:11:44 PM4/25/15
to julia...@googlegroups.com
> "https://github.com/JuliaLang/julia/pull/3292"
> Interesting, has it been implemented now?
No, check the bottom of the thread. But you can just use the macro:

https://github.com/JuliaLang/julia/pull/3292/files

Kevin Squire

unread,
Apr 25, 2015, 3:32:40 PM4/25/15
to julia...@googlegroups.com
The @delegate macro does exist and is used (internally) in DataStructures.jl.

Cheers,
   Kevin

Jameson Nash

unread,
Apr 25, 2015, 3:55:51 PM4/25/15
to julia...@googlegroups.com
It sounds great in theory, except that it returns an object of the wrong type half of the time (for example, similar, copy, sub, slice, etc.) and half the time it will happily do an invalid or meaningless operations (such as taking dot products of Cubes or mutating them to have the wrong dimensions) simply because such operations are reasonable on the base type. And that's just generally worse than useless.
Reply all
Reply to author
Forward
0 new messages