Class hierarchies seem to me more and more like a shadow language[1]. I first realised this in Python, where:
- 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?
Indeed, the Ceylon constructors even read like a regular old static factory method in any other OO language, another pointer to the functional underpinnings of objects.
Sure. A class is certainly a kind of function: one which returns a closure over its own shared declarations. That's the way we conceptualize the notion of a class in Ceylon, and it's why the syntax for a class looks so much like the syntax for a function, and why there's no essential difference between a function defined inside a class (a "method") and a function declared toplevel, or inside another function, or whatever. Ditto for a value defined inside a class (an "attribute") and a value declared toplevel or inside another function. It's the same thing, as far as Ceylon is concerned.
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.
I understand, but once you start taking into account other requirements, like the ability to functionally decompose construction of this record, you will wind up with something that looks painfully similar to a class in Ceylon.
Can you elaborate on that? Since in this model constructors are just functions you can of course define multiple constructors that may or may not call each other.
Well the issue in my mind is how to functionally decompose construction of a record. You have to come up with some way to express a "partial" record, and then "augment" it with new members. I'm trying to say this without using the word "inheritance" ;-)
Right, so at that point, as I say, there's really not much daylight between "class" and "record". The difference being that a class protects its initialization logic (favoring modularity), whereas a record opens it up (favoring flexibility).
Ultimately I see them as much more similar than they are different.
The difference is that a class combines several things into one construct (initialization, type definition, naming) that are separate with records. This is also what causes you to need such an elaborate design for constructors.
What I'm trying to say is that I don't think that the total complexity or total expressiveness is ultimately much different. You trade class types + interface types for record types + named structural types. You trade class inheritance for record extension. You gain a tiny little bit of flexibility via structural typing - but in my opinion much less than is often claimed, since in all these languages so-called structural types are only partially structural - and you lose some power at the tooling level (refactorings are no longer guaranteed to work). You gain a bit of flexibility by decoupling initialization into a function, but you also gain a bit of verbosity and lose a little bit of modularity.
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.
It's highly dependent on coding style. If you program in a mostly ML-like style then I think that design has its advantages because only only need record of functions + open recursion + named + always constructed via some constructor in 1% of cases. In such a style you view that combination as just one combination in a whole spectrum of data types that you can express. However if you want to make a language for object oriented programming it makes sense to build that combination into the language rather than supplying each part as a separate building block that you compose in exactly the same way all the time.
> 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.
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 :)
As alluded above, the biggest problem with the constructor syntax in languages that borrow from C++ is that in the common case of a class with just one constructor, the parameters of that constructor aren't available in the body of the class, leading to awful code like the following:
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:
Right, exactly. So there's two very distinct cases here:
- 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.
Because the post is dealing with the new functionality we've added in Ceylon 1.2, which provides support for Java-style (i.e. multiple) constructors, not the functionality that has always existed in Ceylon, where parameters of the class are listed at the top of the class declaration.
I think the name of the constructor itself may have merit eventually, but is rather distracting from the function of the constructor itself.
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.
So it seems to me that if you need multiple constructors then it's worth being able to give them names, in order to identify their purpose.
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?
Yeah, OK, I just had a quick look, and Scala constructors seem to have almost nothing in common with the design I outline in this blog, other than being, well, constructors. Perhaps you didn't read the linked article?
Yes of course. I'm at the gym right now, so give me a chance to get home, read up on Scala so I can be sure I'm not saying anything wrong, and then I'll reply. Is that OK?
Please look at "case classes" and their associated constructors, which is where I see the most similarity. I am sure they are implemented very differently, but I am not sure exactly what the benefits of ceylon might be over a more "cluttered" scala-style approach built on top of java class-and-interface MRO.
OK, so, here’s a tentative list of similarities / differences. Please correct me if I've made a mistake.
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?
Looks like you are missing the point completely with your focus on Scala constructors:
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! :-)
> Nobody needs or wants the mess of Java-style initialization Ceylon tries to replicate.
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.
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.
> fact is that constructors are called on the class/type, and not on the instance.
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.
I never said I've never looked at Scala. I think you can tell from my responses to you in this thread that I'm in fact quite knowledgeable about Scala. What I said was that I had not looked closely at how Scala does constructors and that they were not an inspiration behind the design of constructors in Ceylon.
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. :-)
There is a large overlap between what you call the default constructor and scala case classes—there is a primary "value" parameter list, and other constructors must be defined in those terms.
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.
Not sure what scares me more: A language designer which doesn't want to give credit where credit is due, or a language who really didn't do his research.
Too bad that Ceylon/Dart still haven't caught up with the state of the art in terms of "named/default constructors".
OK, well it seems quite likley you're just trolling me here, but I'll respond anyway: do you have some technical feedback on how the design I've outlined here is flawed or inferior to some other "state of the art" design, or a link to some research that you think I didn't do?
Because if not, it doesn't seem that scary, does it?
The whole idea of "let's encourage people to litter their classes with convenience constructors" is bad.
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.
Well in fact constructors aren't that much like static methods at all. Now static methods really are a weird thing, because they break the block structure of the language: they look like they have access to members of the class, but they don't.
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.
Wait, Scala's "case classes" are an implementation of what are called "sum types" in PL theory. Ceylon also has sum types, but they don't have anything much at all to do with _constructors_.
If you want to know about how Ceylon supports sum types, you can look here:
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?
Given you're a language designer, I'm really surprised you have not looked at Scala enough to know how it handles constructors.
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...
I guess I don't understand what you're saying here. I was already aware of the concept of a constructor, from years of Java. And I already had a set of really extremely tight design constraints in terms of the existing language syntax and semantics, including a set of principles about how the block structure of the language works, rules about definite initialization, the fact we don't have overloading, etc, etc, which are quite specific to _Ceylon_, and aren't found in other similar languages. These constraints were pretty much sufficient to fully determine the resulting design.
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.
Well since zero credit is due to Scala (read my post below where I utterly demolish the claim that Ceylon constructors are similar to Scala) then doesn't that seem like an utterly rude and obnoxious demand to you?
Name the "uncanny" similarities please. If you can't, I might think you're trolling.
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.
Wait, you're not talking about constructors here. You're talking about the idea of giving a class a parameter list? A feature that I independently came up with about 7 years ago, before I - or most other people - had even heard of Scala? And then implemented 5 years ago as one of like the very earliest features in the Ceylon type checker? And which is not actually the topic of the linked blog post? You're calling me out for copying that?!
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.
Certainly, it happens all the time. Leibniz and Newton invented calculus at the same time. Scala is 12 years old though, and it's rare for a language designer to not be aware of the main competitor in the same space, which is probably why people assumed that you were aware of Scala.
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 in this thread you're conflating taking credit with mentioning, which are two very, very different concepts. I would not expect you to give credit, but I would expect you to mention relevant competing technologies for a compare and contrast. :)
If you look above in this subthread you'll see the following comment:
> 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.
So if you scroll down, you'll find my tentative evaluation of Scala's support for constructors. I have not found anything of interest there. Indeed, if my understanding is correct, constructors in Scala have extremely limited capabilities, since all initialization ultimately flows through the primary constructor which is responsible for initializing all fields. In Ceylon we obviously had a more ambitious set of requirements.
> Indeed, if my understanding is correct, constructors in Scala have extremely limited capabilities
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.
Wait, wait, wait: just two hours ago you were demanding that I give credit to Scala for the design of constructors in Ceylon. And you accused me of quote "just tweaking of the language's constructor patterns and calling it novel".
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 two hours ago you were demanding that I give credit to Scala for the design of constructors in Ceylon.
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 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 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.
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.
No, because that pattern is still strictly more powerful. With enumerated classes I can have cases which are a class rather than just a singleton instance.
Ceylon is my favorite language and I can't wait for v1.2 to be out! I just wish it was catching up with JDK release a little faster and generally speed up the release cycle. Spread the word, it definitely needs more love!
77 comments
[ 0.21 ms ] story [ 126 ms ] thread- 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 :)
Well that seems like a laudable goal. Let's take a look at the example code, then, to see what the alternative is:
Maybe it's just me but it looks like the pain is still there?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.
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.
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. 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!
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) You conveniently left out the other part of the sentence.
B) I already commented on that topic very early on, and clarified:
To which you – confusingly – replied with: So what do you actually want? 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.
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 :-/