Iād say that Chris is 100% right about this one. Just think about immutable types that get inlined ā how are you going to pack more bits into the same amount of memory because you have mix-ins?
Put another way: what specifically is going to change about the memory layout of a type with a mix-in? Formulating the exact layout optimizations you have in mind is a great way to convince people to make a change like this.
I agree with you that it would be a good idea to have something for this. But I wouldnāt expect any performance gains: it would likely just be syntactic sugar to essentially do what the macros do. You can probably even implement a package for interfaces using macros if youād like, and submit that as what Base should look like.
I am 90% confident that Julia specifies the memory layout of types like isbits immutables as part of its current semantics (in order to allow FFI, for example) and just informs LLVM that types have the layout that Julia defines. Hopefully someone like Jameson will correct me if Iām getting that wrong.
You prompted that digression by pointing to potential performance gains, which are clearly not applicable because macros are called at compile time and therefore give the same runtime performance as builtin syntax.
If your point is about elegance, then just say it. The best way to improve things is to describe existing macros and what you consider as their limitations. Then propose better solutions (e.g. syntax).
Note that thereās an intermediate solution between new syntax and macros provided by packages: thatās macros provided in Julia Base. For example, enums can be considered as a fundamental feature, and yet they are provided via @enum rather than via a special syntax, just because the macro offers everything we need. Often such macros can be first implemented in packages, and once the design has stabilized and many users are happy with them they can be moved to Base (at least when they correspond to a common enough need).
Note that this doesnāt really apply to Julia. Essentially everything available to Base is available to package developers. Things donāt have to be built into Base (or be written in C) to be fast, and syntax doesnāt even have to be declared in Base to exist (in macros). So you can make your own highly performant syntax and just tell people to put a macro around the type definition:
@inheritable immutable SuperType
a
b
end
# Now inherit the fields
@inherits immutable MyType <: SuperType end
Implement that and now you have a form of inheritance of fields which is as optimized as it would be if it was in the Base library. Lots of basic things, like the advanced arrays and anonymous functions, start off in packages.
I think youāve raised an important point here and one that deserves a good answer. Itās been a while since I looked at the manual but, as far as I know, we donāt have any real documentation that addresses this ā which is particularly likely to cause confusion when everyone is to the Java-esque solution.
Generally speaking, Julia has been used quite widely and in very large (~100kloc) codebases, so itās unlikely that itās missing obvious features that would make this stuff very difficult. In most cases, it turns out that Julia handles this stuff fine ā but perhaps take a different approach compared to other languages.
In this case, Julia uses āhas-aā rather than āis-aā relationships. Trivial example:
type Noise
sound::String
loud::Bool
end
makenoise(n::Noise) = println(n.loud ? uppercase(n.sound) : n.sound)
type Dog
wetnose::Bool
noise::Noise
end
Dog(wetnose) = Dog(wetnose, Noise("woof", true))
type Cat
longwhiskers::Bool
noise::Noise
end
Cat(longwhiskers) = Cat(longwhiskers, Noise("purr", false))
This approach is going to be weird for those whoāve used more classic inheritance models, but itās arguably more general and powerful. You can inherit behaviour and fields from multiple sources and combine them with total control. Having to explicitly forward methods is a line or two of extra boilerplate (annoying I know ) but thatās relatively minimal. Hereās a post I wrote where I go over a more complex architecture in excruciating detail.
It may take a while to get used to this but I think it should address your underlying issue. Itās been a powerful way to handle all the cases Iāve come across. If you come across a case where that pattern isnāt working for you, weād be more than happy to help figure out either how to make it work or what improvements the language needs to support it.
You may be right in this case but itās not as obvious as you say. There are many things which can be implemented via macros but not made fast with them; generated functions being a prime example. Itās plausible that more optimisations to memory layout may be possible with more static information.
Generally, I think the community should avoid piling on over things like this. Perhaps in an ideal world the question should have been phrased as asking for help rather than as feature request, perhaps it confuses means with ends, perhaps it includes extraneous (or incorrect) detail. But phrasing these questions ācorrectlyā when you donāt know Julia like the back of your hand is hard and people arenāt always going to get it right. Itās easy to attack feature requests but much more helpful to try to understand the underlying issue.
@MikeInnes Thereās no problem with making feature requests that donāt master all the implementation issues at stake. I was just replying to criticism from the OP that Julia people were talking too much about details.
Then you could make Dog <: MakesNoise and Cat <: MakesNoise and āpaintā noise-related method onto those types. The main limitation here is that types canāt inherit from multiple abstract types ā which is a limitation weāve been aware of for some time.
The current high-level language design situation weāre in is that we need some subset of the following features:
abstract multiple inheritance
interfaces
protocols
traits
delegation
field sharing
There are all in roughly the same general area of language design: shared functionality and structure. The hard part is figuring out how to slice up this design space into a few features that are simple to use and understand, nicely orthogonal, have efficient implementations, and donāt do anything unfortunate to the way people use the language. This is a hard design problem and not one that can be solved piecemeal ā if we choose one feature and add it without figuring out the rest of the picture, then the end result will almost certainly not cohere as well as we would like. And once youāve added a feature, itās pretty hard to take it back since thousands of people will already be using it. So for now, since people are getting by well enough with macros and multiple dispatch, weāve decided that this design problem can be tackled after we get a 1.0 version of Julia out the door.
As an aside, I would like to suggest that this not be the case. I think Julia, as a high-level language, should be free to pack its types in the most efficient manner possible in order to exploit the memory hierarchy. This affects immutables, but also tuples etc.
I think choosing the āCā style layout should be an opt-in, not the default. Interop with C is important but it also prevents certain optimizations, some of which could be a distinguished advantage for Julia. Structure layout affects everything from memory size, memory/cache efficiency to parameter passing at the instruction level.
I think a big issue Julia faces in this space is that āinterfacesā are purely āconventionā; thatās really what a lot of this discussion boils down to. My biggest fear wrt this is that you end up with a mixture of the following problems which affect large projects in the below languages:
C++: Templates, and their requirements, are still only specified by āconventionā. For example, you assume that a parameter is an iterator and then try to apply iterator operations to it; if it doesnāt satisfy them, you will get an error; the unfortunate problem is that to novices, the error is frequently completely obscure and usually comes deep within some other framework or the standard library. However, at least it is caught at compile time.
Python: Refactoring large python code bases is a nightmare, in my experience. One of the reasons is that if an interface needs to change, there is no easy way to allow the compiler to tell you āHey, youāre calling this function incorrectlyā; you will only end up with an error at runtime.
These problems are not going to surprise anyone, and both are recognised and addressed by the community; C++ has a Concepts TS. Python 3 introduced function type annotations, and in fact the just-released Python 3.6 has added variable annotations; now a tool - like, for example, mypy - can at least do some analysis and help you catch errors which can be easily diagnosed before runtime.
Iāve been reading some of the related threads on GitHub and saw one proposing, what are effectively Haskellās type classes - or C++ concepts, if you like (I donāt know Scala so canāt comment on Scala protocols). This could address some of the concerns voiced here, and on other github threads. However, before such a facility is available, I think Julia could benefit even from a simpler mechanism - something akin to the notion of āpure virtual functionsā, which could enforce certain requirements being satisfied.
I appreciate the many benefits of a system where abstract classes have no state, and I also read a related discussion on Github from a little while back proposing adding attributes to abstract classes. Introducing state to abstract classes is fraught with a number of problems (see C++ multiple/virtual inheritance), but leaning back on the āpure virtualā notion, I think a near-term, middle of the road solution would be to allow āabstractā attributes in abstract classes. Those have no state; they are again just requirements, and could be implemented using an actual attribute (state) in concrete types, or if the proposals ever come around, using the āgetfieldā functionality Iāve seen @jeff.bezanson and others mention. (As an aside, now that Iāve written this down, I feel like Iāve seen this before. Itās been a few years since Iāve done serious Matlab development but I believe the abstract attribute was something that was added to the ānewā object model when it was introduced around 2007).
I see similar posts to this all the time (i.e. workaround for traditional inheritance) and was wondering if we could consolidate it into the docs somehow.
I feel like each post starts over from scratch with no after-v1.0 end goal in sight