9 Answers
9

In fact, we find more and more that using inheritance is sub-optimal because (among others) it leads to tight coupling.

Implementation inheritance can always be replaced by interface inheritance plus composition, and more modern software designs tend to go into the direction of using inheritance less and less, in favour of composition.

So yes, not providing inheritance in particular is a completely valid, and very in vogue design decision.

Mixins, on the other hand, aren’t even (yet) a mainstream language feature, and languages that do provide a feature called “mixin” often understand very different things by it. Feel free to provide it, or not. Personally I find it very useful but implementing it right may be very hard.

Reasoning about whether inheritance (or any single feature, really) is necessary or not without also considering the rest of the language's semantics is pointless; you're arguing in a vacuum.

What you need is a consistent language design philosophy; the language needs to be able to elegantly solve the problems that it's designed for. The model to achieve this may or may not require inheritance, but it's hard to judge this without the big picture.

If, for example, your language has first-class functions, partial function application, polymorphic data types, type variables and generic types, you have pretty much covered the same bases as you would have with classic OOP inheritance, but using a different paradigm.

If you have late binding, dynamic typing, methods-as-properties, flexible function arguments, and first-class functions, you also cover the same grounds, but again, using a different paradigm.

(Finding examples for the two paradigms outlined is left as an exercise for the reader.)

So, think about the kind of semantics you want, play around with them and see if they are sufficient without inheritance. If they are not, you may either decide to throw inheritance into the mix, or you may decide that something else is missing.

I think not allowing inheritance is fine, particularly if your language is dynamically typed. You can achieve similar code reuse by, for example, delegation or composition. For example, I have written some moderately complicated programs--okay, not that complicated :)--in JavaScript without any inheritance. I basically used objects as algebraic data structures with some functions attached as methods. I also had a bunch of functions which were not methods that operated on these objects.

If you have dynamic typing--and I'm assuming you do--you can have polymorphism without inheritance as well. Also, if you allow adding arbitrary methods to objects at runtime, you won't really need things like mixins very much.

Another option--which I think is a good one--is to emulate JavaScript and use prototypes. These are simpler than classes yet also very flexible. Just something to consider.

As long as there's a reasonable way to accomplish the kinds of tasks typically handled with inheritance, go ahead. You might try out a few use cases, to make sure (possibly even going so far as to solicit example problems from others, to avoid self-bias...)
–
comingstormJan 17 '12 at 1:54

No It's statically and strongly typed, I'll mention that at the top.
–
ChristopherJan 17 '12 at 2:43

Since it's statically typed, you can't just add random methods to objects at runtime. However, you can still do duck-typing: check out Gosu protocols for an example. I used protocols a bit over the summer and they were really useful. Gosu also has "enhancements" which are a way of adding methods to classes after the fact. You could consider adding something like that as well.
–
Tikhon JelvisJan 17 '12 at 2:51

2

Please, please, pretty please, stop relating inheritance and code reuse. It is long known that inheritance is really bad tool for code reuse.
–
Jan HudecJan 17 '12 at 8:45

2

Inheritance is only one tool for code reuse. Often it is a very good tool, and other times it is horrible. Preferring composition over inheritance does not mean never using inheritance at all.
–
Michael KJan 17 '12 at 14:00

There are in fact very good reasons to remove implementation inheritance, as it can produce some extremely complex and hard to maintain code. I'd even go so far as to regard inheritance (as it is typically implemented in most OOP languages) as a misfeature.

Clojure, for example, doesn't provide implementation inheritance, preferring to provide a set of orthogonal features (protocols, data, functions, macros) that can be used to achieve the same results but much more cleanly.

Here's a video that I found very enlightening on this general topic, where Rich Hickey identifies fundamental sources of complexity in programming languages (including inheritance) and presents alternatives for each: Simple made easy

When I first ran across the fact that VB6 classes don't support inheritance (only interfaces), it really annoyed me (and still does).

However, the reason it's so bad is that it didn't have constructor parameters either, so you couldn't do normal dependency injection (DI). If you have DI then that's more important than inheritance because you can follow the principle of favoring composition over inheritance. That's a better way to re-use code anyway.

Not having Mixins though? If you want to implement an interface and delegate all the work of that interface to an object set through dependency injection, then Mixins are ideal. Otherwise you have to write all the boilerplate code to delegate every method and/or property to the child object. I do it alot (thanks to C#) and it's one thing I wish I didn't have to do.

To disagree with another answer: no, you throw out features when they're incompatible with something you want more. Java (and other GC'd languages) threw out explicit memory management because it wanted type safety more. Haskell threw out mutation because it wanted equational reasoning and fancy types more. Even C threw out (or declared illegal) certain kinds of aliasing and other behavior because it wanted compiler optimizations more.

Only that explicit memory management and type safety are completely unrelated and definitely not incompatible. Same applies to mutation and “fancy types”. I do agree with the general reasoning but your examples are rather badly picked.
–
Konrad RudolphJan 17 '12 at 10:42

@KonradRudolph, memory management and type safety are closely related. A free/delete operation gives you the ability to invalidate references; unless your type system is able to track all affected references, that makes the language unsafe. In particular, neither C nor C++ is type safe. It's true that you can make compromises in one or the other (eg linear types or allocation restrictions) to make them agree. To be precise I should have said Java wanted type safety with a particular, simple type system and unrestricted allocation more.
–
Ryan CulpepperJan 17 '12 at 12:06

Well that rather depends on how you define type safety. For instance, if you take the (wholly reasonable) definition of compile-time type safety, and want reference integrity to be part of your type system, then Java isn’t type safe either (since it allows null references). Forbidding free is a quite arbitrary additional restriction. In summary, whether a language is type safe depends more on your definition of type safety than on the language.
–
Konrad RudolphJan 17 '12 at 12:31

1

@Ryan: Pointers are perfectly type-safe. They always behave according to their definition. It may not be how you like them to, but it's always according to how they're defined. You're trying to stretch them to be something they're not. Smart pointers can guarantee memory safety fairly trivially in C++.
–
DeadMGJan 17 '12 at 13:13

@KonradRudolph: In Java, every T reference refers to either null or an object of a class extending T; null is ugly, but operations on null throw a well-defined exception rather than corrupting the type invariant above. Contrast with C++: after a call to delete, a T* pointer may point to memory that no longer holds a T object. Worse, doing a field assignment using that pointer in an assignment, you might update a field of an object of a different class entirely if it just happened to be placed at a nearby address. That's not type safety by any useful definition of the term.
–
Ryan CulpepperJan 18 '12 at 0:19

Do you think it's okay not to allow inheritance or Mixins? (given that the user would at least have interfaces)

You can do a lot of very useful work without implementation inheritance or mixins, but I wonder whether you should have some kind of interface inheritance, i.e., a declaration that says if an object implements interface A then it also needs to interface B (i.e., A is a specialization of B and so there is a type relationship). On the other hand, your resulting object need only record that it implements both interfaces, so there's not too much complexity there. All perfectly doable.

The lack of implementation inheritance has one clear downside though: you will be unable to construct numeric-indexed vtables for your classes and so will have to do hash lookups for every method call (or figure out a clever way to avoid them). This could be painful if you're routing even fundamental values (e.g., numbers) through this mechanism. Even a very good hash implementation can be expensive when you hit it multiple times in every inner loop!

If you want to remove a base language feature, then you are universally declaring that it is never, ever necessary (or unjustifiably diffficult to implement, which does not apply here). And "never" is a strong word in software engineering. You would need a very powerful justification to make such a statement.

That's hardly true: Java's whole design was about removing features like operator-overloading, multiple inheritance and manual memory management. Additionally, I think treating any language features as "sacred" is wrong; instead of justifying removing a feature, you should justify adding it--I can't think of any features every language must have.
–
Tikhon JelvisJan 17 '12 at 1:36

5

@TikhonJelvis: Which is why Java is a terrible language, and it's lack of anything except "USE GARBAGE-COLLECTED INHERITANCE" is number one reason why. Language features should have to be justified, I agree, but this one is a base language feature- it has plenty of useful applications and cannot be replicated by the programmer without violating DRY.
–
DeadMGJan 17 '12 at 1:54

Speaking from a strictly C++ perpective, inheritance is useful for two main purposes:

It allows code resusability

In combination with overriding in a child class, it allows you to use a base class pointer to handle to a child class object without knowing its type.

For point 1 as long as you have some way to share code without having to indulge in too much acrobatics, you can do away with inheritance.
For point 2. You can go the java way and insist on an interface to implement this feature.

The benefits of removing inheritance is

prevent long hierarchies and its associated problems. Your code-sharing mechanism should be good enough for that.

Avoid changes in parent classes from breaking child classes.

THe tradeoff is largely between "Dont Repeat Yourself" and Flexibility on one side and problem-avoidance on the other side. Personally i would hate to see inheritance go from C++ simply becuase some other developer may not be smart enough to forsee the problems.