ceylon / ceylon-spec

DEPRECATED
Apache License 2.0
108 stars 34 forks source link

remove constructors #1438

Closed zamfofex closed 8 years ago

zamfofex commented 8 years ago

1. Wait... seriously?

Constructors are something I disliked from the moment I saw a thread about them. However, other people seemed to share the opposite opinion. With time, they gained more and more popularity until they finally got implemented. Because of that, I honestly tried liking them. And I still am. I spent about a couple hours a week thinking about it, trying to lead myself into liking them, but whenever I compared, the negatives always won over the positives for me. The more I thought about it, the more I distanced from what I was trying to achieve - which was actually liking them. Constructors feels to me like a big mistake. Like one of those mistakes that people did a long time ago, but are now are so embedded into stuff that it's impractical to revert them. Except that I feel like there is still time to fix it, but that time is fading away. In fact, who made me actually decide to finally create this thread and share my opinions was @gavinking. When I asked if I should create it, he negated, and, paraphrasing it, added "I don't want you to spend time on something that's not going happen". I can't say I agree with him in that regard, but it made me realize, that the more I wait, the less likely it is to happen. It made me wonder why I haven't done anything earlier. Even after that, I held off doing this, but I realized all I was doing by sitting there disliking constructors and taking no further action was letting time slip past through me.

But again, literally everyone else seems to disagree with me. In fact, the only person I know that disliked them was @gavinking, but he changed his opinion. It makes me really wonder if I'm just wrong, but can't see it yet. I want to be wrong... I want to be able to look at this neat new feature and enjoy it, like everyone else is doing. But above it, I don't want to have the differing opinion. I don't want to be the one guy who dislikes constructors and can't get over it. As I said, I'm still trying to prove myself wrong, but I honestly can't do it, the more I try the more I distance myself from it. I concluded that accepting that, and creating a thread about it is the best thing to do. The worse that can happen is it getting closed and stuff moving on like nothing happened, which would be effectively the same as me not creating a thread. However, I don't want to be a bother, I strongly hope I'm not, and I'm really sorry if I am.

2. Okay. Why?

So all I've done is claim that I don't like constructors, but I have yet to say a word on why. Everyone believes that their code would be better with the addition of constructors. I want to make clear that _I _don't* disagree*. If contributors do not get removed (probably the case), I won't disagree that using them could improve my programs a lot.

However, I don't think that's the right approach language-design wise. I think that using them could make my code better, but that doesn't mean I believe the language is better with them. From a language design perspective, I think that this is a bad way to make people's code better. I believe that there are much more general, regular, and intuitive approaches to this situation. Even if they don't quite fix all the problems constructors do, they reach a more broad set of improvements than constructors do (not forgetting the "more regular and intuitive").

2.1. The problem with constructors

Constructors break a major regularity rule of the language. Before, all declarations within a class were members of instances of that class. This has huge implications over the regularity of the whole language. A class could be seem as simply a function that happen to be able to allow some of its local members to be accessed later, after it has finished executing. Classes are the mix of interfaces and functions, declaring both a type and a callable expression; being able to both execute code, and have and inherit shared members. Constructors come as some weird hybrid betwen a declaration and a control flow structure. They stand as the one exception to many statements that could be otherwise done.

They are the only declaration that is exclusive to one structure, all other can be used anywhere - interfaces, classes, function, top level. They have a completely different semantic than any other declaration, while, misleadingly, having a similar-looking syntax.

They modify their containing class' syntax, which is just bonkers regularity-wise! No other structure get close to do anything like that. It is terribly irregular semantics-wise. This is not just a special rule exclusive to constructors, but it is also a special rule that is completely different, and does something completely different, than all other special rules in the language do. It have a much clearer effect than the other special rules do.

2.1.1 They aren't even that great

As I said before, I won't disagree that, if contractors don't go away, I admit they will make my codes better, and that they do indeed fix the problem they came to fix. However that's pretty much about it. What bothers me most about constructors is that they are mostly useless. They come to fix a very specific set of problems that don't even occur that often. When this problems do occur, sure, the workarounds aren't pretty, but, again - they don't occur that often. They are clearly problems that do indeed occur, and I think constructors were introduced with those problems in mind, and not much else. I feel like the fallacy of the arguments in favor of constructors when I propose workarounds, or different approaches, is that my suggestion don't do as good against these problems, but people fail to consider that there is more that is affected.

3. My proposal

Honestly, constructors can't do much more than what could already be done without them. You can simply imagine that the class they are inside being an interface, and them being just classes that satisfy that interface. For example:

shared class Foo
{
    shared String str;

    shared new bar()
    {
        str = doSomething();
    }

    shared new baz()
    {
        str = doSomethingElse();
    }
}

would become

shared interface Foo
of bar&baz
{
    shared formal String str;
}

shared class Bar()
satisfies Foo
{
    str = doSomething();
}

shared class Baz()
satisfies Foo
{
    str = doSomethingElse();
}

Using this sort of coding style is definitely not quite as powerful as constructors are. However, combined with module/package privacy, this does indeed get close.

An suggestion I have to make this more comfortable to write, is something I call function classes (But I'm terrible with names). They are similar to constructors, but they can be used outside the class' body.

new hello()
{
    print("Hello, World!");
}

hello(); // Prints "Hello, World!"!

They look a whole lot like functions! However, they are effectively simply classes with lowercase name.

Using those things, nearly everything useful constructors can do, can also be done. Here are a couple examples:

shared abstract class Point()
{
    shared Float x;
    shared Float y;
}

shared object origin
extends Point()
{
    x = 0.0;
    y = 0.0;
}

shared new cartesian(shared actual Float x, shared actual Float y)
extends Point()
{}

shared new polar(Float r, Float a)
extends Point()
{
    x = r * cos(a);
    y = r * sin(a);
}

Point a = origin;
Point b = cartesian(1.0, 2.0);
Point c = polar(1.5, pi/2);

shared interface Person
{
    shared formal String name;
    shared formal Integer age;
}

shared new person(shared actual String name, shared actual Integer age = 16)
satisfies Person
{
    assert(0 <= age < 100);
}

shared new clonePerson(Person cloned)
satisfies Person
{
    shared formal actual String name = cloned.name;
    shared formal actual Integer age = cloned.name;
}

shared interface Programmer
satisfies Person
{
    shared formal Language favoriteProgrammingLanguage;
}

shared new programmer(String name, Integer age, shared actual Language favoriteProgrammingLanguage = ceylon)
satisfies Programmer
extends person(name, age)
{}

shared new cloneProgrammer(Programmer cloned)
satisfies Programmer
extends clonedPerson(cloned)
{
    shared formal actual Language favoriteProgrammingLanguage = cloned.favoriteProgrammingLanguage;
}

Interestingly enough, I didn't come up with this syntax to replace constructors. I just noticed that It was very common in my codes functions/methods that merely had a inner class and a return statement that returned a new instance of the class. Of course, now I could just use the new inline objects, but I thought that a dedicated structure could be neat. At first thought, I felt like it was an unneeded feature, but, after thinking a little about it, I noticed how well the syntax worked with the rest of the language. Again, it is literally just a class, but the catch is that it can override methods. Using the sealed annotation could indicate that it may not be used in a extends clause outside the module its declared. This means that you can at any time exchange function classes for regular functions, and vice versa.

module/package privacy and function classes work well together fixing the problems constructors came to fix. I feel like this is a much more general approach to the situation. They might not solve the problem as fully as constructors do (but they get close), however, they individually, and together with other language features, also help improve a lot of other parts of the language, besides being much more regular. (I also think function classes could be neatly used for annotations!)

4. manythanks~

If you've read all the way through, or even if you just skipped everything and jumped to this paragraph, thanks for bothering to check out what I have to say! Really, I got to thank everyone on Gitter who listened(read?) to stuff I had to say. Not exclusively, but particularly programming is a topic I don't have anyone to talk with. Having people actually care about my thoughts and things I have to say - even if they don't agree - is kind of a luxury to me, and it feels really neat. And again, I'm really sorry for bringing up this topic once again, but I just couldn't convince myself to not to...

cheers!

ePaul commented 8 years ago

I understand your point (and agree with it) that constructors violate the language regularity.

I'm not totally sure what your "function classes" do – if I understand right, these are just classes which can't be used as types, and therefore have lower-case names (but they still can be used for extends-clauses of other classes)? What is the advantage over regular classes?

(You should look over the syntax of your examples again, I think the Point examples miss some extends clauses.)

zamfofex commented 8 years ago

@ePaul

I'm not totally sure what your "function classes" do – if I understand right, these are just classes which can't be used as types, and therefore have lower-case names (but they still can be used for extends-clauses of other classes)? What is the advantage over regular classes?

That's right, they are just classes. They don't offer proper advantage besides convenience, just like objectdeclarations... Function classes allow you to override methods, and replace functions from one version of your module to another. I forgot to mention, but I thought about allowing people to use an annotation (maybe sealed, but probably something else) to prevent different modules from using function classes in extends clauses. This annotation would also prevent you from declaring any shared methods that aren't either actual. This effectively makes your function class indistinguishable from a regular function to the outside world, which means that you can freely replace functions with function classes and vice-versa... They can also be useful for declaring annotations. I feel like using top-level function classes to declare annotations to be a much better idea than to use constructors and have special-case imports for the language module that allow you to use just shared instead of SharedAnnotation.shared... Function classes could also directly satisfy annotation interfaces, meaning that you could simply have one single structure to declare an annotation.

(You should look over the syntax of your examples again, I think the Point examples miss some extends clauses.)

Oh yeah, that's true. derp~... xP Thanks!

Thanks for reading and commenting! =D

quintesse commented 8 years ago

Closing as requested by @zambonifofex. See https://gitter.im/ceylon/user?at=5648fe328b242470793de961