10

I am developing my own programming language. It's a general purpose language (think staticlly typed Python for the desktop, i.e. int x = 1; ) not intended for the cloud.

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

For example: Google Go, A systems language that shocked the programming community by not allowing inheritance.

Christopher
  • 265
  • 1
  • 7

9 Answers9

13

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.

tdammers
  • 52,406
  • 14
  • 106
  • 154
9

Yes.

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.

So, all told, I'd go for it.

Tikhon Jelvis
  • 5,206
  • 1
  • 24
  • 20
  • 2
    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...) – comingstorm Jan 17 '12 at 01:54
  • No It's statically and strongly typed, I'll mention that at the top. – Christopher Jan 17 '12 at 02: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](http://protocols.github.com/) 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 Jelvis Jan 17 '12 at 02:51
  • +1 for prototype-based inheritanse. It could be tricky to implement right in a static language, though. – 9000 Jan 17 '12 at 04:38
  • 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 Hudec Jan 17 '12 at 08:45
  • 3
    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 K Jan 17 '12 at 14:00
8

Yes, it's a perfectly reasonable design decision to omit inheritance.

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

mikera
  • 20,617
  • 5
  • 75
  • 80
  • In many cases, interface inheritance is more appropriate. But if used with moderation, implentation inheritance can allow removal of a lot of boilerplate code, and is the most elegant solution. It just requires programmers who are more intelligent and more careful than the average Python/JavaScript monkey. – Erik Alapää Dec 15 '16 at 12:36
4

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.

Scott Whitlock
  • 21,874
  • 5
  • 60
  • 88
4

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.

Konrad Rudolph
  • 13,059
  • 4
  • 55
  • 75
2

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.

So the question is: What do you want more than inheritance?

Ryan Culpepper
  • 1,413
  • 9
  • 8
  • 1
    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 Rudolph Jan 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 Culpepper Jan 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 Rudolph Jan 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++. – DeadMG Jan 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 Culpepper Jan 18 '12 at 00:19
  • @DeadMG: no, their behavior after deallocation is *undefined*. From the C++ spec: "... the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined." (ISO/IEC 14882:1998, section 3.7.3, paragraph 4, p48) – Ryan Culpepper Jan 18 '12 at 00:42
  • @Ryan But it’s not compile-time ensured. I agree that Java fails more gracefully but this is completely irrelevant here. Neither C++ nor Java have a compile-time ensured type system *which prevents null references* (and yes, this *is* possible). My entire point is that “type safety” is not an absolute quality, and what constitutes type safety varies from language to language. Your claim that ”that’s not type safety by any useful definition” is simply wrong. Also, consider that you can also do the same in Java, using reflection, so the same caveat applies here. – Konrad Rudolph Jan 18 '12 at 10:56
1

No.

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.

DeadMG
  • 36,794
  • 8
  • 70
  • 139
  • 8
    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 Jelvis Jan 17 '12 at 01:36
  • 7
    @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. – DeadMG Jan 17 '12 at 01:54
  • 1
    @DeadMG wow! Pretty strong language. – Christopher Jan 17 '12 at 02:48
  • @DeadMG: I started writing a rebuttal as a comment but then I turned it into an answer (q.v.). – Ryan Culpepper Jan 17 '12 at 04:01
  • 1
    "Perfection is achieven not when there's nothing left to add but when there's nothing left to remove" (q) – 9000 Jan 17 '12 at 04:39
1

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

  1. It allows code resusability
  2. 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

  1. prevent long hierarchies and its associated problems. Your code-sharing mechanism should be good enough for that.
  2. 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.

DPD
  • 3,527
  • 2
  • 16
  • 22
1

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!

Donal Fellows
  • 6,347
  • 25
  • 35