Constructors in Ceylon
ceylon-lang.org
ceylon-lang.org
- Classes can be called like functions
- Calling a class runs a function (the constructor, called __init__)
- The constructor is implicitly passed a fresh object as an argument (usually called "self"; this is like an implicit use of "new")
- The constructor implicitly returns an initialised object (a mutated "self")
In a language with first-class functions, it's also pretty arbitrary to define "attributes" inside the constructor and "methods" outside, since methods are functions which are values (modulo the implicit binding stuff).
Hence classes can be completely replaced by their constructors; or in other words, classes are a design pattern when writing functions. This is a realisation that's become mainstream due to Javascript, and that's the first thing I thought of when I saw the Ceylon code in this article.
The details at the end, about execution order, partial constructors, inheritance, etc. all screamed "shadow language" to me: if you've already got functions(/methods), can't you just re-use them instead of defining a new, semantically distinct category?
[1] http://gbracha.blogspot.co.uk/2014/09/a-domain-of-shadows.ht...
One of the design goals in Ceylon was to treat all these things uniformly, so that a class is, as much as possible, just like any other function.
However, there is a big and surprisingly important difference between a class and a function that returns a record: with a class you get open recursion between members of the class. You don't get that with a function that assigns members to a record type - or, at least, if you do build the machinery you need to get that, you have essentially reinvented classes with a worse syntax.
Open recursion seems like a small thing. But in fact, in practice, if you try taking it away from me, I will have to do some convoluted and nasty things to emulate it. Sure, there are plenty of simple classes where you can do without it, but there are surprisingly many classes where it's necessary, or at least very useful.
To support open recursion, a lot of functional languages consider differently top level functions and functions inside functions. That's why in JavaScript you have a murky global object with some weird scope rules that makes top level functions globally available.
Ultimately I see them as much more similar than they are different.
Ultimately it's a bit of a wash and honestly I would probably be almost as happy with OCaml or Rust or something like that as I am with Ceylon. I think both systems work well and are intellectually consistent. I don't think either is clearly superior to the other.
That's reassuring to know, thanks :)
> However, there is a big and surprisingly important difference between a class and a function that returns a record: with a class you get open recursion between members of the class.
Record members are certainly bound too early to get open recursion, which is why I claimed that classes == functions rather than objects == records ;)
In my example of Python, "objects" are abstract: created implicitly, passed into the constructor, then (implicitly) returned.
> You don't get that with a function that assigns members to a record type - or, at least, if you do build the machinery you need to get that, you have essentially reinvented classes with a worse syntax.
I think there are a few distinctions to be made here:
- Records vs. objects; which I've addressed above.
- Syntax vs. semantics: I'm fine with special-purpose syntax, I just don't like arbitrary semantic distinctions. It sounds like Ceylon is better than most in this regard :)
- Built-in vs. library code: reinventing classes using functions is only a bad idea if they're already built into the language in some other way. If there's no built-in alternative, then doing this in a library is legitimate. Likewise, using the language's existing support for functions to build-in a class implementation is also legitimate, and it sounds like that's what Ceylon's done :)
class Point {
public float x;
public float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public String toString() {
return "(" + x + ", " + y + ")";
}
}
This hurts. Fortunately, we've already made that pain go away in Ceylon.Well that seems like a laudable goal. Let's take a look at the example code, then, to see what the alternative is:
class Color {
shared Integer rgba;
//default constructor
shared new (Integer rgba) {
assert (0 <= rgba <= #FFFFFFFF);
this.rgba = rgba;
}
...
string => "Color { \
alpha=``hex(alpha)``, \
red=``hex(red)``, \
green=``hex(green)``, \
blue=``hex(blue)`` }";
}
Maybe it's just me but it looks like the pain is still there? class Point(shared Float x, shared Float y) {
string => "(``x``, ``y``)";
}
I think I made that really clear in the post. In the very example I started with, but perhaps I need to further clarify it somehow?UPDATE: FTR, I added the above example to my post, in the hope that it clarifies the point. Thanks for the feedback.
- the simple and overwhelmingly common case where there is only one way to instantiate a class, and therefore there is no reason to decouple the body of the class from the parameter list, and
- the occasional but also important case where there are multiple ways to instantiate the class, each with different parameter list signatures, where we need to decouple the body of the class from the parameter list signatures.
"Constructors" as supported in Java, C++, C#, and now also in Ceylon, are optimized for the "occasional" case. And I'm convinced that they're useful enough that we need them. But in the "overwhelmingly common" case they get in the way, and that's why they're not the usual thing you reach for first when writing a class in Ceylon.
Color(this.rgba);
That is, it directly assigns the argument to the slot (which must be defined at class-level.)Of course, it's still repeating the class name but that's solvable.
Does that make sense now?
Seems similar to C# with its "Primary Constructor":
http://odetocode.com/blogs/scott/archive/2014/08/14/c-6-0-fe...
Much of the rest of the work seems to mirror what Scala did several years ago, and this seems to nearly replicate that. Considering the state of the scala codebase, this again might have merit, but they do not even mention it once. As such, I'm going to relegate this to the "branded language that will not survive outside advertised setting" category because it seems to prioritize positive comparisons over engineering use.
I have never looked at how Scala handles constructors, beyond being dimly aware that Scala has them, so I don't see why I should be expected to mention Scala, when it had no influence at all on the design of this functionality. To be clear, I find it highly unlikely that Scala constructors are very similar to what I describe in this post, except perhaps on the most superficial level. I will take a look at Scala later today to confirm that, just in case I'm wrong.
I'm not sure what argument is being made in the rest of your comment. Perhaps you could elaborate on what are your concerns?
Similarities:
- Scala has the notion of a “primary” constructor vs “auxiliary” constructor, which at first looks sorta superficially similar to the notion of a “default” vs “named" constructor in Ceylon.
- Both Ceylon and Scala (and Java, and C++, and C#, etc) have a notion of delegation between constructors.
Differences:
- In Scala, constructors are overloaded, that is, they are distinguished by the types of their parameters. In Ceylon, they have distinct names, and are distinguished by name.
- In Scala, every auxiliary constructor of a class must ultimately delegate to the primary constructor. In Ceylon, there is is no such restriction. Any constructor may delegate directly to the superclass. Given this, I can't see how it's possible for two constructors of a Scala class to delegate to different constructors of the superclass. That's possible in Ceylon.
- In Scala I could not find any way to assign to an immutable member (val) from an auxiliary constructor. At first I thought that this could not possibly be a real limitation, but, checking stack overflow, it seems that it is. Auxiliary constructors can only assign to vars? Really?!
- In Ceylon, initialization flows from the top of the class body to the bottom, allowing. In Scala it jumps around: all statements of the primary constructor are executed, even statements that occur after the auxiliary constructors, and then the auxiliary constructors.
- Ceylon has the notion of a partial constructor which partially initializes the class, but may only be called by other constructors. Scala doesn’t seem to have this concept. Indeed, the primary constructor must initialize all fields. And, if I understand correctly, the only thing that auxiliary constructors are allowed to do is mess with mutable members and perform side-effects. (Of course, it’s a bad practice to make constructors side-effecty.)
- Ceylon has the notion of a value constructor. I have not yet found anything like that in Scala.
- Taking a reference to a constructor and treating it as a function seems to be quite uncomfortable in Scala. In Ceylon it's very natural. Also Scala sometimes seems to demand the use of the "new" operator in instantiation expressions, though I'm not clear why that's a requirement.
My bottom line conclusion is that constructors in Scala are much less powerful than I had imagined they would be, and, it seems, much less powerful than constructors in Ceylon. Of course, some of what I’ve written here could be incorrect, since I’m not a Scala programmer. If so, I’m hoping someone will correct me.
Anyway, I definitely haven’t found anything in Scala constructors that we should have reproduced in Ceylon. Can someone else find something?
Less is more. Nobody needs or wants the mess of Java-style initialization Ceylon tries to replicate.
Ceylon has the notion of a value constructor.
I have not yet found anything like that in Scala.
Yet another static thing in class declarations? Inventing different names for "static" doesn't solve the issue that you have introduced both "static" and "object" into the language. That's not a good idea. Also Scala sometimes seems to demand the use of the "new" operator in
instantiation expressions, though I'm not clear why that's a requirement.
It's the same reason why instanceOf/casting is x.isInstanceOf[X]/x.asInstanceOf[X] instead of adding "convenience" syntax. You can't discourage people to use something and then provide syntax sugar for it.Constructors in Scala are intended to directly initialize fields. They shouldn't be called directly, and are often private. Factory methods provide everything else, and are declared in objects, instead of being static like in Ceylon. That's by-design.
Because you care about the "outcome", not the "process": Scala did away with 90% of the mess associated with constructors, and used existing general-purpose features of the language to provide the rest (instead of having to introduce named constructors, default constructors, value constructors, partial constructors, and constructs which are "static" but named differently ...).
I don't think anything can change your idea that the thing you invented is the best thing ever (and everything else doesn't apply, because of the special constraints of Ceylon), therefore have a nice day! :-)
(Secondary) constructors are discouraged.
Initialization in Ceylon is nothing like initialization in Java:
- For the simple (common) case where there is exactly one initialization path, Ceylon is far less verbose.
- In Ceylon, the compiler guarantees that ever field not marked variable is assigned exactly once.
> Yet another static thing in class declarations?
Not really, just a constructor with no parameters.
Constructors are not "static". Constructors access the members of the class.
> Constructors in Scala are intended to directly initialize fields.
But it seems to me that they can't. Isn't it correct that only the primary constructor can initialize vals?
> Constructors in Scala are intended to directly initialize fields. They shouldn't be called directly, and are often private.
AFAICT it is not a limitation of the Scala language that constructors shouldn't be called directly. If it's indeed a practice that constructors aren't called directly, then it's interesting to enquire why that might be. And indeed an answer presents itself: because they don't have names.
> Factory methods provide everything else, and are declared in objects, instead of being static like in Ceylon.
Wait: a factory method declared on a "companion object" is not like static?! Really?
And it seems to me that people probably hide constructors behind factory methods precisely because constructors in Scala don't have names. I mean AFAICT, the syntax for calling a factory method of a companion object in scala is exactly the same as the syntax for calling a constructor in Ceylon! You just have to go through a whole lot more ceremony in Scala.
> Scala did away with 90% of the mess associated with constructors
And, AFAICT, also lost like 75% of their capabilities. Unless my evaluation above is wrong, and I'm missing something. But so far no-one has spoken up to correct me.
> therefore have a nice day! :-)
You too!
Constructors are not "static". Constructors access the members of the class.
That doesn't mean that they are not "static". Yes, they sit in a weird middle-ground, but fact is that constructors are called on the class/type, and not on the instance. Color foo = ...; new foo; // doesn't make any sense.
If it's indeed a practice that constructors aren't called directly, then it's
interesting to enquire why that might be. And indeed an answer presents itself:
because they don't have names.
No, because it is good design to have one entry-point to create an instance, not 5.
It is hard enough with reflection, sun.misc.Unsafe and serialization as-is. Wait: a factory method declared on a "companion object" is not like static?! Really?
In Scala you have one place for "static" things. In Ceylon, static is all over the place: - Static members inside objects
- Static methods inside classes (with new x() {...})
- Static fields inside classes (with new x {... }
I mean AFAICT, the syntax for calling a factory method of a companion object in
scala is exactly the same as the syntax for calling a constructor in Ceylon!
Yes. Ceylon wasted the "good" syntax on the wrong construct. Most of the patterns leveraged in factory methods are just not available in Ceylon, because the best syntax was directly married to the class. And, AFAICT, also lost like 75% of their capabilities.
Which has not been a issue in the last 10 years of Scala.
Everything Ceylon has built into "constructors" are just standard methods with no magic required in Scala. Unless my evaluation above is wrong, and I'm missing
something. But so far no-one has spoken up to correct me.
Yes, you are focusing on the power of constructors. Yes, constructors are not extremely powerful – because they don't need to be in Scala. You are missing that Scala provides that power without turning constructors into such a mess.But this is the case in Scala too. So I really don't understand the distinction you're trying to make.
> No, because it is good design to have one entry-point to create an instance, not 5.
I don't understand how having a factory method on a Scala companion object that calls an overloaded constructor of a Scala class is not a separate "entry-point". I count each of those factory-method-constructor bundles as one entry-point. And even if these factory methods all call the same constructor, they still seem like separate "entry-point"s.
And apart from your assertion that a single "entry-point" is good design, I don't quite see any particular reason to believe it. You've offered no arguments for this assertion.
> In Scala you have one place for "static" things. In Ceylon, static is all over the place:
Scala has members of objects and constructors of classes. Ceylon has members of objects and constructors of classes. Where is the difference? OK, so in Ceylon I can have a constructor which does not declare any parameters. How does that amount to being "all over the place" compared to Scala.
> Yes. Ceylon wasted the "good" syntax on the wrong construct. Most of the patterns leveraged in factory methods are just not available in Ceylon, because the best syntax was directly married to the class.
Well you see this is where Scala starts to look really bad. The problem is that Scala has no plain functions. Scala forces you to write methods of objects. In Ceylon we have toplevel functions, so we just don't need to go around sticking functions on the side of classes like you do in Scala.
> Everything Ceylon has built into "constructors" are just standard methods with no magic required in Scala.
This is simply false. You can't emulate constructors with plain methods in a language which enforces immutability / single-assignment. If you could, we would have done it that way.
> Yes, constructors are not extremely powerful – because they don't need to be in Scala.
Well, I never claimed that Scala needed constructors. Indeed I never mentioned Scala until people started trying to say—incorrectly to the point of absurdity, as it turns out—that Ceylon had copied its system of constructors from Scala.
Indeed you were one of those people. You entered this discussion with the following attack on me:
> A language designer which doesn't want to give credit where credit is due
I'm still waiting for you to retract that, now that I've conclusively demonstrated that Ceylon's constructors are totally different to—and more powerful than—Scala's.
A language designer which doesn't want to give credit
where credit is due
I'm still waiting for you to retract that, now that I've
conclusively demonstrated that Ceylon's constructors are
totally different to—and more powerful than—Scala's.
Eh. What?A) You conveniently left out the other part of the sentence.
B) I already commented on that topic very early on, and clarified:
Ok ... I'm really starting to believe you when you said you
didn't look into that other language, because there is
practically no benefit in doing it the way Ceylon does.
To which you – confusingly – replied with: I never said that. Please don't put words into my mouth.
So what do you actually want? Ceylon's constructors are totally different to—and more
powerful than—Scala's
Yep. I didn't deny that. It's a good thing though, because Ceylon's approach isn't very good in terms of language complexity.Whatever, your attack on me was totally uncivil and unjustified, as you now realize, which is why you're backpedalling it so furiously. You've never interacted with me before, and so coming in here with a blazing personal attack was completely unreasonable behavior. I think you see that, so let's just drop it now.
> Ceylon's approach isn't very good in terms of language complexity.
Here's another assertion for which you simply have not provided evidence. How are Ceylon's constructors more "complex" than Scala's constructors? The actual syntactic weight of both constructs is almost identical. And in terms of complexity, the factory-method-on-a-companion-class pattern is significantly more complex in terms of ceremony than doing the equivalent thing in Ceylon.
Look man, stepping back a second, I can see that you're clearly a fan of Scala and that Scala is something you love and enjoy. That's great! But Scala isn't perfect and other languages can have good ideas too. I highly recommend you spend some time learning Ceylon, since it has a bunch of awesome things in it that I know you'll love: the things we can do with union and intersection types, disjointness, abstraction over tuple and function types, etc, are just beautifully elegant and powerful. Don't let the fact that you love Scala blind you to other ideas.
the things we can do with union and intersection types,
disjointness, abstraction over tuple and function types,
etc, are just beautifully elegant and powerful. Don't let
the fact that you love Scala blind you to other ideas.
I don't think it's a blind love, but all those things will ship in one of the next versions of Scala, too.Given there are some things I can't just suffer anymore, like unreadable Generics (<>), required ; and "Type ident" syntax, maybe Ceylon just isn't for me. :-)
Well it's really lovely and encouraging to see Ceylon exercising such a strong influence on Scala. Makes me very proud.
My comment was not meant to be an argument, just a note that there are similarities between this and the other major contender for "java/javascript replacement" and you don't bother to mention them. If they are different, you don't bother to say that either. So it's not a very useful without the comparison, it's just tweaking of the language's constructor patterns and calling it novel.
Too bad that Ceylon/Dart still haven't caught up with the state of the art in terms of "named/default constructors".
Because if not, it doesn't seem that scary, does it?
Constructors are the odd things which act like static methods, but have access to the fields of the instance-to-be-created.
Because constructors are weird and special in a lot of other ways too, it makes sense to use them only for initialization, and try to encourage people to define exactly one constructor.
Named constructors are poorly designed factory methods with the disadvantage that they are married to the class and add additional baggage to class declarations.
Why does Ceylon have objects, if they aren't used for one of their prime use-cases?
"Static methods go into objects, except static factory methods, let's keep dumping them into the class declaration" ... looks quite questionable to me.
Ok ... I'm really starting to believe you when you said you didn't look into that other language, because there is practically no benefit in doing it the way Ceylon does.
That's why Ceylon doesn't have static methods.
Constructors, OTOH, actually fit in with the block structure of the language. They do have access to the members of the class, and the responsibility to initialize those members.
Now, the interesting thing is that I spend 4 years of my life hoping that we would not need constructors, and that the ability to initialize a class via the parameter list of the class would be sufficient for all usecases.
Sadly, it turns out that I was wrong about this, and that, in practice, there are some really good usecases for constructors in a language which emphasizes immutability as Ceylon does. That's just what we ran into in practice.
> you said you didn't look into that other language
I never said that. Please don't put words into my mouth.
FYI, I've looked closely at lots of other languages, including languages like OCaml and F# and Rust which do things quite differently to Ceylon. I like those approaches, and they have advantages/disadvantages compared to constructors. I would not say they are clearly better, nor clearly worse. But I'm confident that what we have done is very clean and elegant and powerful in the context of Ceylon.
If you want to know about how Ceylon supports sum types, you can look here:
http://ceylon-lang.org/documentation/1.1/tour/types/
FYI, Scala didn't invent sum types, and they can be found in languages much older than Scala, going back to the 1970s, if I'm not mistaken.
Finally, I don't understand why you're repeating the claim that we're "just tweaking of the language's constructor patterns and calling it novel" when I've already responded and explained that the approach to constructors in Ceylon is quite different to Scala and wasn't influenced by Scala. Is it just a lame attempt to try and provoke me or do you have an actual point to make?
Scala is also your biggest competitor (IMO) on the JVM, which makes it even more odd that you seem to not have looked at it deeply. (I'd understand not reading the latest DOT type calculus/whatever paper, but this is basic syntax/usage.)
Totally your call of course, but stealing the best ideas, in any endeavor, is usually the best way to go. Even if you decide to not steal Scala's constructors, that's fine, it's awesome if you have something better, but to have not even evaluated them...
Just surprised.
Furthermore, there was an issue open in Ceylon's issue tracker for almost 2 years, following on from discussions and proposals in two other issues that go back even further, and these issues were read and commented on by many of our community members, including some who have some Scala experience.
Now, if you could point to some cool idea in Scala that we obviously aren't aware of, and which could have improved the design of constructors in Ceylon, then I guess you would have a point. But it doesn't seem like you do have anything concrete, just a concern about _process_ rather than _outcome_.
And fundamentally I'm a guy who cares about outcome. And I think the outcome is rather excellent.
To be fair, it's nice to see you acknowledge that there are "obviously some differences". Some tiny little subtle differences like how in Ceylon, I can actually have two constructors that both initialize an immutable field. Like how constructor resolution is by (unambiguous) name and not by (potentially ambiguous) overloaded parameter types. Like how different constructors can delegate to distinct constructors of the superclass. 'Cos, y'know, other than that, it's really just a copy of Scala.
Absurd.
Man, seriously, there is such a thing as two people coming up with the same good idea independently. If you can't imagine such a thing happening, I'm at a total loss.
Note that I personally couldn't care less who invented what, I just wrote what I thought duaneb and stephen were getting at to clear up the confusion since you said "I guess I don't understand what you're saying here."
> I think what they're saying is that it's good to give credit where credit is due.
I don't think I was conflating. Or at least, if I misinterpreted, then so did at least one other person. Which suggests that the original comments were very open to such an interpretation.
Is this not desirable? The wider the definition of a constructor is, the less you can determine from looking at the construction of a resource/variable/whatever. When you delve into e.g. the creation of an ORM this becomes extremely important because it has to reason about data constraints across multiple initializations.
It seems that you now accept that in fact the two designs are dissimilar, and that Ceylon's is significantly more powerful.
So, before continuing this discussion, do you think it would appropriate to start by apologizing to my team?
Just so we don't get off on the wrong foot?
No, I demanded you reference a similar language to actually compare the benefits of your changes. If you do, I suspect you'll find you gain virtually nothing.
> Much of the rest of the work seems to mirror what Scala did several years ago, and this seems to nearly replicate that.
I have already demolished this claim on this thread, as you very well understand.
I personally make it a policy to admit error when I err. It's up to you to decide whether this is also your policy, or whether you prefer goalpost-shifting and prevarication.
> No, I demanded you reference a similar language to actually compare the benefits of your changes.
I make it a firm policy to not talk about other new languages when I blog about Ceylon, because whenever I have mentioned Scala in the past, even just in passing, I have found myself the subject of quite vicious personal attacks. I'm going to continue with that policy, this thread notwithstanding. But thanks for your feedback anyway.
I'm sorry to hear that. That's certainly not a productive way to have a conversation, though, and it's rather confusing you'd try to argue for the benefits of your hard work without making them in terms of languages people actually use.
Yes, just for clarification, that was my point.
You're certainly having to defend yourself a lot in the other comments, so I don't want to continue that.
The order of Ceylon initialization is nice. That is a pita in Scala.
OK cool. Then my response is simply that we took a whole lot of community feedback, as always, over a span of years, both on the issue tracker and on our Gitter (previously IRC) channel. People with a wide range of programming experience contributed to this discussion.
No, I did not look closely at Scala's constructors. I did look at some other languages which are a bigger influence on me, some of which do things very differently to Ceylon.
> You're certainly having to defend yourself a lot in the other comments, so I don't want to continue that.
Thanks :-/