OOP in Julia, inherit from parametric composite type

question

#1

Hey,

so this might be a rather vague question but anyway here it is. I am really having a hard time getting to grasp with OOP in Julia. I am developing a package for specific clinical trial designs and these designs can essentially be represented as a tuple of vectors. Trying to be thorough I implement them as parametric composite types. E.g. the base design could be

type DesignA{T1, T2}
  v1::Vector{T1}
  v2::Vector{T2}
end

Now another design could be just like DesignA but with additional parameters.

type DesignB{T1, T2, T3}
  v1::Vector{T1}
  v2::Vector{T2}
  additionalParameter{T3}
end

In C++ etc. I would simply inherit from DesignA and get all methods etc. for free. In Julia, however, I cannot subtype parametric composite types (right?). So I introduced an abstract type Design as supertype of both DesignA and DesignB and implement all methods for the base Design for this abstract supertype. E.g.

getv1(d::Design) = d.v1

This does, however, not allow to specify the return type of getv1 (which I would like to do both for safety reasons and clarity) and in general all methods for Design cannot use the type information T1, T2 that every Design instance has.

An alternative that came to my mind was to define Design as a concrete type and use some kind of a mixin approach:

type Design{T1, T2, PType}
  v1::Vector{T1}
  v2::Vector{T2}
  additionalParameter{PType}
end

My `getv1’ would then be something like

getv1{T1, T2, PType}(d::Design{T1, T2, PType})::Vector{T1} = d.v1

I think that should work fine in my particular case, but it makes later extensions of the code difficult because nobody can inherit from `Design’ and all extensions must fit in the mixin approach i.e. define a new PType and dispatch based on that.

So, please be patient with a statistician trying to wrap his head around the delicacies of OOP and help me out here. What is the Julian way of handling such cases in the clearest possible way?

Best,

Kevin


#2

There are two issues here:

  1. allowing the compiler to generate efficient code,
  2. reusing code when extending behavior.

Regarding (1), don’t worry about it too much. The first version of getv1 will compile for concrete types, and will be efficient. It should not be necessary to document return types, the compiler will figure it out (in general, methods should just be type-stable.

Regarding (2), see the discussion here.


#3

Okay, thanks a lot for the clarification. Should work fine for anything I could think of. I am just very skeptical about how this scales to larger projects with multiple collaborators when the interfaces which you have to program against can be scattered over multiple files oO.
Also, without enforcing return types I can never rely on a defined interface as someone might screw up return types in his/her implementation…
Sometimes Julia is really quite driving me nuts, love it though.

I think my approach via parametric mixins is a little different than what is suggested in the discussion that you mentioned. I essentially say Design{T1, T2, Float64} is a Design{T1, T2, PType} just as Design{T1, T2, Int64}. So, in a way, Design{T1, T2, PType} can be seen as an abstract supertype of any concrete instance’s type, which allows specialization of methods based on the mixin types. If I do not restrict PType in the definition of Design{T1, T2, PType}, this allows arbitrary “inheritance” by defining a new type “new” which holds the additional structure and specializing the relevant methods to Design{T1, T2, new}. So the only thing I am lacking is that I cannot enforce a specific return type for new mixins as their method specializations might return something entirely different…


#4

Julia does not lend itself easily to C++-style OOP, true. But this is natural. Consider the following: you define methodA which maps something to Type1 objects, methodB which works on these objects and, say, an iterable. Someone uses your library as

methodB(methodA(something), itr) 

which is fine. Then you extend and define Type2, and sometimes methodA returns objects of this type. Then it is your responsibility to extend methodB accordingly. In good practice, libraries “own” a type and take care of these things. There are still holes you don’t catch at compile time though, that’s the price for a dynamic language. Unit testing mitigates this to a certain extent.


#5

I think maybe part of the issue is that julia isn’t an object-oriented programming language, and thus difficulties often arise when people try to implement OOP designs in Julia. From your description it is hard to see whether a different simpler organisation might solve the needs - eg, could your designs go in a simple Dict or DataFrame?