Restriction on field names in a user-defined composite type?

question

#1

Hello! I want to define my own composite type for my dataset, e.g., the following is a simpler version of what I want to define:

type MyData
 data::Matrix{Float64}
 length::Int64
 size::Tuple{Int64,Int64}
end

When I wrote inner constructors for this type, I got an error, and apparently, julia does not like the field names such as length and size since they are predefined function names in the Base module. Can we get away from this kind of restrictions? Of course if I replace length and size in MyData by len and dsize, everything works. But in general, we should have flexibility in naming fields of any composite types. I would appreciate your comments!
Best,
BVPs


#2

Works for me on Julia 0.5:

julia> type MyData
        data::Matrix{Float64}
        length::Int64
        size::Tuple{Int64,Int64}
       end

julia> t = MyData(rand(3,3), 4, (5,5))
MyData([0.399807 0.051389 0.0959585; 0.42537 0.215592 0.777495; 0.293881 0.0591327 0.335123],4,(5,5))

julia> t.length
4

julia> t.size
(5,5)

julia> t.data
3×3 Array{Float64,2}:
 0.399807  0.051389   0.0959585
 0.42537   0.215592   0.777495
 0.293881  0.0591327  0.335123

#3

Ah, didn’t see that it was the inner constructor that was the problem. It’s impossible to help without seeing what you tried, though.


#4

I think you might be mistaken about what the problem is. Having field names that conflict with exports from Base is not an issue, regardless of whether there are inner constructors.

It would be very helpful if you could post more context so we could help diagnose the problem.


#5

This works for me on 0.6

julia> type MyData
           data::Int
           length::Int
           size::Int
           MyData(data, length, size) = new(data, length, size)
       end

julia> MyData(1, 2, 3)
MyData(1, 2, 3)

Of course since I also use them as argument names, they shadow the global names and you can’t use them directly as global functions anymore.


#6

Hi, @dpsanders and @yuyichao. Thanks a lot for your feedback. I need to give you a bit more detailed type definition, which is still simpler than my real type, but it already shows the problem. Let me define my type and constructors as follows (note: they are still a bit artificial):

type MyData
    data1::Matrix{Float64}
    data2::Matrix{Float64}
    length::Int64
    size::Tuple{Int64,Int64}

    MyData(data1,data2,length,size) =
      if isempty(data1)
        error("data cannot be empty!")
      elseif size(data1,1) != size(data2,1)
        error("inconsistent number of rows!")
      else
        new(data1, data2, length, (size(data1,1),size(data1,2)+size(data2,2)))
      end
end

MyData(data1) = MyData(data1, Matrix{Float64}(), size(data1,1), size(data1))
MyData(data1,data2) = MyData(data1, data2, size(data1,1), (size(data1,1),size(data1,2)+size(data2,2)))

Now, in my julia session, I got the following error:

tmp=MyData(eye(2), eye(2))
>MethodError: objects of type Tuple{Int64,Int64} are not callable

and the error points to the line: elseif size(data1,1) != size(data2,1).

Note that if I replace the fieldname size in MyData by datasize, I don’t get any error and it works correctly.


#7

Can’t you use Base.size?


#8

Thanks, @kristoffer.carlsson !
It works if I replace my use of the function size in the inner constructor by Base.size. Then, I can use the fieldname size in MyData. But I have one question. In my outer constructors, i.e.,

MyData(data1) = MyData(data1, Matrix{Float64}(), size(data1,1), size(data1))
MyData(data1,data2) = MyData(data1, data2, size(data1,1), (size(data1,1),size(data1,2)+size(data2,2)))

I didn’t have to replace size function by Base.size function. Why the outer constructors accept this while the inner constructor does not?


#9

Because you don’t have an argument named size in those functions so you are not shadowing the exported Base.size.


#10

Thanks a lot, @kristoffer.carlsson . May I ask you one more question? In my definition of MyData type, the most critical input arguments are data1 followed by data2. The correct values of the fields length and size in MyData object can be determined completely once data1 and data2 are given. What I want to do is like my outer constructor, i.e., tmp=MayData(data1,data2) automatically fills the correct values of tmp.length and tmp.size. To do so, do I still need to write the inner constructor and outer constructors like I did? Is there any better way to achieve the same? I feel my current code is a bit awkward.
Thanks for your help!
BVPs


#11

You can just write the inner constructor with 2 arguments (data1, data2), like you did for the outer constructor:

type MyData
    data1::Matrix{Float64}
    data2::Matrix{Float64}
    length::Int64
    size::Tuple{Int64,Int64}

    function MyData(data1::Matrix{Float64}, data2::Matrix{Float64})
        if isempty(data1)
            error("data cannot be empty!")
        elseif size(data1,1) != size(data2,1)
            error("inconsistent number of rows!")
        else
            new(data1, data2, Base.size(data1,1), (Base.size(data1,1), Base.size(data1,2)+size(data2,2)))
        end
    end
end

If you want a single argument (data1) constructor, you can provide an outer constructor:

MyData(data1::Matrix{Float64}) = MyData(data1, Matrix{Float64}(size(data1,1), 0))

or use a default argument to the inner constructor:

function MyData(data1::Matrix{Float64}, data2::Matrix{Float64} = Matrix{Float64}(size(data1,1), 0))

#12

Thank you very much, @greg_plowman ! Perhaps, the inner constructor with the default arguments would be neater. I tested it and worked well!