Scala’s rules on constructors are very irritating. At least I think they are. I’m a beginning Scala user and could be very wrong.
Using Java classes with constructors is very irritating. Classes like RuntimeException have to primary constructors. The null constructor, “()” and the detail constructor “(message: String)”. Making an exception class in Java is a common pattern. However, I can’t find a way to call the super constructors. I do understand the difficulty in languages with multiple inheritance (classes and traits). It is less than straightforward.
Using “extends” does signal the main inheritance class, so those could be used for identifying the proper “super”. Both traits and classes may be used for the main line. That creates a discord between using “extends” and “with”. A trait constructor might be available or might not be available. As with method calls, the actual method being executed is extremely complex.
CLOS (Common Lisp Object System) does allow a solution. CLOS is fundamentally different than most Smalltalk based languages (C++, C#, Java, Scala). In those languages there are few, if any functions. There are only methods. The functional languages have true functions, but the only can be created withing classes or traits.
CLOS classes have no methods. There are only functions. Structural multiple inheritance exists at the class level, but that only defines structure. Traits in scala is kind of like that, but there are implied accessors as well as methods. Even structure is difficult to manage with traits.
The primary difference between scala’s multiple inheritance and CLOS’s is CLOS only has functions. There are no methods. That means that multiple inheritance only exists at the function level. A single define function, bound to a symbol, takes zero or more arguments. Each argument can be typed and the type can be a class graph. Again, the class is only structural. The method can specialize on the class graph.
Here comes the big difference. The function may also be overloaded (using the scala definition). The parameter can any type graph. Class graphs and overloading class graphs are identical as far as the language is concerned. In scala, the equivalent would be a single virtual type that is a class that is a subclass of all of the class and overloaded types. The closest that scala gets to this are traits inherited via “with.” This gives scala all of the complicated type graphs of CLOS, but few of the benefits.
In Smalltalk derived languages, every function has an implicit first argument (self, this). But the function is a method, not a function. It is bound to a class. The true functions may not implement true multiple inheritance. They are limited to a single class graph. The parameter is a hamstrung type. CLOS has no methods, and must handle the type graph differently than scala. It is more general. It is equivalent to types that can be multiple, optional +T.
To deal with the issue, it has the call-next function call. It solves the same type calculus as the class/trait calculus. As we all know, with both +T and -T type graphs. In the large, this might not stop. In reality, it does. The developer’s brain would blow up. Programmers quickly learn not to approach the problem. CLOS only has +T type graphs [last I used it]. The type graph does halt. This allows call-next to determine what to call next. In scala, type calculus is declarative. That makes it run-time cheap. A dynamic function call graph never happens at run time.
Mostly - monads complicate the problem with higher level functions. Scala does do a good enough job. It punts when it doesn’t. Not halting means there will always be some times where there is no solution. Either the compiler will terminate, hopefully with an error, or hang trying to compute non halting type calculus. At runtime, it is an infinite loop.
So the problem exists in scala with methods. Scala doesn’t allow this with constructors as a special case. It gets close to pretending it does “def this” but doesn’t really handle the problem. It treats constructors as second class methods and not at all like functions. The problem isn’t addressed with constructors, somewhat handled with methods, almost handled with higher level functions. Calling (or blocking) the +T graph super methods is just skipped. With high level functions, -T can cause problems. But (I think) not at run time. Where it can’t handle it, the problem is just punted. Punting makes the language simpler, but irritating.
What is really irritating is that constructors are handled differently than every other method. Functions can be overloaded as methods, they can’t be overloaded as constructors. That irritation can be handled by making constructors no different than any other method.
The proposal is
- Treat constructors no different than any other method. Limit call-next to “extends” and disallow “with”. Call-next is limited to super calls. Traits still get treated differently depending on how they are declared.
- Treat all methods the same, don’t treat “extends” and “with” differently. Use “super-next”.
2.5) Treat overloading no different for methods and constructors. - Treat all functions the same.
The options are ordered by relatively simple to difficult implementation. Constructors are no different than methods. The limits and generosity of both is annoying. The really are identical. Both constructor and method functions just have an implied first argument.