-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement class declarations #32
Comments
Nah. We should avoid doing this, for two reasons:
So, no
|
Thinking about this one more iteration, this is a feature. Notice in particular that you can still get per-instance closure semantics by providing your own methods through the constructor syntax. This leads to a situation where the class author controls the per-class mutable state, and the class consumer controls the per-instance mutable state. And there's a bit of a hand-over and negotiation taking place during object construction, in which the consumer gets the last word. I like everything about that. |
It's funny, we use the
Array-like types! Currently, we have three types that would benefit from being constructed that way. In Perl 6 terms, it would be The tricky part would be how to declare them. I've gone through a number of different possibilities, but no syntax feels perfect:
This one is very clear, but it presupposes #33 and type annotations, which are not supposed to be core (while the class declaration syntax is). Then I thought maybe this form:
This works, but then I felt it clashes in an unfortunate way with the proposed fixed array types syntax. (Through note the lack of My current favorite is this:
That is, no new syntax. If the class happens to have (a) exactly one attribute, (b) which happens to be the I know it's a little bit "magical". But I think it will have fairly few false positives. And I can't think of a solution with less egregious flaws. Oh, and this one doesn't presuppose type annotations. If you do have type annotations loaded, there would also be a (c) condition: the annotated type of the attribute is Basically, the values constructed would be Naturally, all the functions/methods that work on ordinary arrays ( (Note: |
Can we use somethign like |
Another issue not addressed yet: subtype relations. I'm a bit hesitant with this one, because I think class inheritance tends to be abused much of the time to shunt methods around between classes and smear them out across a code base. Also, given that what we already have relies quite heavily on lexical scopes and implicitly created constructor forms, straightforward If not for that, we could've simply done
and been done with it. Here's what I think we should try instead. A class ...so maybe we should do both, as a kind of failsafe? 😋 Seriously, though, I think just relying on implicit subtyping as above would be fine, at least for the rather small |
As I'm using this in practice, it becomes clear that this is a general phenomenon where I just saw the specific case of the array until now.
Both of these feel very natural too. The common denominator is not a single array attribute, but a single attribute, period. By the proposed "attr name mirrors class name" convention, the classes would be implemented like this:
It strikes me that I would also be OK with the convention being that the sole attribute be called |
And yes — in practice this gives us Ven++'s |
Thinking about this a bit more: the one place where I see this working less well is for empty class declarations. From the 007 sources:
So every So maybe just give up and write it explicitly as I think the prefix thing is still worthy, though. Maybe the thing we should keep is that a class that declares itself a subtype must also declare all the attributes of its supertype. (In any order.) Under the typing regime, we'd also require that these attributes all be assignable to the corresponding attributes in the supertype. (That is, they can be the same type or narrower.) |
And all the methods, presumably. I wonder how tempted we'd be to have some syntactical convenience for allowing methods to be "inherited" over from base classes. Interestingly, this makes it very feasible to also have multiple inheritance. Under the typing regime, there'd even be some welcome conflict detection, in which the same method name with wildly different signatures will simply be un-implementable in the deriving class. |
Not to mention that this would mesh quite well with #41. In fact, I think that settles it: |
Rethinking the MOP and the names a little, I think this is what I want:
I'm already going with the |
There's a total collision between subcalls (That space there is just convention. It's good for humans, but 007 ignores it.) The verdict here is that if This is the best we can do (same as Perl 6, by the way). The alternative is to hedge the parser and go eeee maybesubcallmaybetuplecreation, and leave something weird in the Qtree that can collapse later... and we're not doing that. We have enough interesting problems as it is. |
Having tried out objects in 007 a bit, I think the above design decision would be a mistake. Private state is available through lexical closure in normal objects. Not having that feature through classes would be a great pity, and would make Plain Old Objects strictly more powerful (and more desirable) than classes. Below is the current implementation of the
Here's what I would like to be able to write with the
Both the object form and the Here are the significant differences from what has been proposed so far in this issue:
|
-1 to changing |
It is lexical. What's changed isn't the behavior of the |
We kinda have this, except that we're conservatively restricting the allowable statements to |
I think it would be nice to start implementation of this feature pre-v1.0. I suggest we try doing it behind a feature flag (rather than a branch). I don't want v1.0 to block on classes, but it might benefit everyone to have actual classes to try out before v1.0. |
One interesting thing that's always worked in Elixir, and feels nice (though a bit surprising): class bodies are compile-time evaluated context.
(yes, |
At first glance, I'm surprised to see an unquote outside of a quasi. But I guess that's an Elixir thing? |
Yes, that's how they do "executable class bodies". The for's body is quoted |
I see. Though I'm unsure what you're suggesting here for 007. Would you like the Is there somewhere I can read about Elixir's compile-time evaluation of class bodies? |
I'm just trying to create a discussion about how languages with macros without a MOP usually manage those things. |
Ah, I see. Yes, that discussion is very much worth having. Thank you for bringing it up. I have no idea how the balance will come out between macros/Q and MOP... but in some ways I'm not so troubled by it, as long as they interoperate well enough. |
Thinking about this, I realize what we do need a good story for is how to inline a bunch of stuff (attributes and methods) into a class using unquotes and macros. Something like this:
Or like this:
We should take a leaf from JSX here, which essentially allows an array of elements and text nodes to be injected in element position; the array melts away, and the elements and text nodes are injected in sequence without any intervening structure. |
Belatedly I'm coming around to this. It's a lot more reasonable for I hope to throw up a complete class syntax/semantics proposal soonish. |
(...where "soonish" apparently means "more than one month later"...)
The four possible member declarations, in other words, are:
Private properties and methods I imagine will be handled with Symbols as described in #207. In other words, they'll be inaccessible from outside view. Both the declaration and every use in the class is desugared behind the scenes to deal in symbols. You access a private property or method as I like this syntax. The only objection I have myself is that |
The above proposal also completely avoids changing the behavior of |
I came up with a variant that's virtually keyword-free:
I think I like that better. It's strangely consistent both with the 007 object syntax (except semicolons separate members instead of commas separating properties) and with Perl 6's private-slot twigil (except in 007 it's a sigil, kinda). |
The method
I feel C# got it right with the |
...oh, but does that syntax mean that we again run into trouble with |
However, if we allow We can try to make
Maybe allowing that will have unintended consequences, such as users declaring all their methods as sub expressions or arrow functions? Ah, gotta trust the users on this one. It should be a redeclaration error to try to declare a parameter |
As soon as we allow both property initializers and constructors, the question naturally arises which order they should run in. I'd say property initializers first, then the constructor, without hesitation. Furthermore, initializers in base classes should also run before the constructor, ordered from base class to derived class. (In other words, in declaration order.) The above follows C#'s design. Java does it a bit differently — it inlines the initializers into each class's constructor. This ultimately can lead to surprises during initialization, especially when a base class's constructor calls a mehod overridden by a derived class, which in turn expects the derived class's initializers to have run. They have in C# but not in Java. |
I realize I need to think more about what exact MOP instructions the various bits of the class syntax (properties, constructors, methods, private/public) desugar to. |
...and inheritance. After we have those things in place, we can remove the "needs more design" label from this issue. To be honest, I think such a design would help with the issues identified in #266 also. In particular, it would help nail down how enums and modules (our two "special" meta-types right now) would work. |
Not to add to all the self-bikeshedding on syntax (except really yes)... I was enjoying The Left Hand of Equals when on page 8 this syntax showed up:
(The I was struck by how neatly class definition is made into a type of function/routine definition. Specifically, the constructor is represented as parameters on the class body itself. I'm surprised I haven't seen this in a language somewhere. Maybe it exists but I'm unaware of it? Anyway, I'm wondering if we should steal it for 007. (Note that Python uses parentheses after the class name too, but for inheritance, as in the Here's how I would write the above as 007 code, maybe:
Here, the I'm thinking any additional constructor logic one might want can just be written at the top of the class body, maybe? Note that this pretty squarely gives the class one constructor... but 007 already had it that way, so that's OK. Does this play well with inheritance? Well, kinda:
Here, the In this case, This comment is just a bikesh^Wsuggestion. I'm torn, but kinda curious. I find the syntax suggestive, not least because it would rhyme well with #307 and (even moreso) #308. (In that class declaration and class construction would look alike, just as function declarations and function calls do.) To top it off, this syntax (and |
I think the strongest argument against a I think the biggest confidante here is our tiebreaker language, JavaScript, which indeed puts parameters on its "classes" in the old-skool constructor syntax. But that's only barely comparable. Secondly, in the new ES6+ class syntax, JavaScript explicitly does not go that route, even though it could. (It would be interesting to know if TC39 discussed a syntax like that.) Maybe simply the fact that it's an unusual syntax, whereas 007 tries to be as syntactically "uninteresting" as possible, is an argument against. |
Coming back to this issue — ow, my head, all the ideas — I think this one means that initializers can't just be sugar for initializations in the constructor; they need to be their own thing. This complicates things a little bit. But I suspect that extra complication might still be better than Java's surprising behavior. |
I think we need to start somewhere. The very smallest bit of syntax/semantics I can see that would be useful is just public properties and public methods. This in itself would help improve I'm still very much on the fence about whether to prefer the long syntax or the short syntax. For now, let's go with the short one. But having any of them would be useful and open up new things to try in 007. |
Holy gosh! Simula does it this way! 😱 |
This changes everything! (😛) |
In this talk, Hejlsberg laments a little bit that JavaScript didn't get a syntax where the parallel between a class and a function were made more explicit and the class took in its constructor parameters as a parameter list after the class name. |
Beyond all the syntactic self-bikeshedding I've inflicted on myself, I'm plagued by a number of orthogonal Tough Tradeoffs on the semantic level:
|
Re some of the above questions, I found it useful to think of it in the very concrete example of the following method lookup:
Where does it find (the functional value representing) the There needs to at least be a distinction between fields and methods in the sense that a field gets found immediately on the instance, whereas a method goes through the above dance. Still, the interface looks like a property access in both cases. |
Yet another dimension to this: is it possible to make methods behave indistinguishably from func/arrow function props? |
The solution to this conundrum is this: make the safe syntax the default. Have the exotic syntax available through a module. There needs to be a way to displace/supersede grammar rules -- probably something with exactly the same category and declarative prefix as an existing rule needs to have an I'm thinking it'd also make sense to conservatively forbid an already displacing rule to be displaced by another rule... In the future when we understand the dynamics better, we might relax that. |
But Simula (and C#) do it that way, so it's not innovating ;-). |
C#, really? Not in this brief introduction to C# classes. Could you link me to where C# does this? Anyway, even with some of those languages having that syntax, it would be "exotic" for 007 to step away from the way Perl 6, Python, and JavaScript do it — which differs in the details but is otherwise fairly consistent. |
Well, I suppose what I say was misleading. The feature I’m talking about is called « records » but is only coming with C# 8. |
And C#'s upcoming records don't appear to be the same, syntactically, as Simula and what I'd like for 007's "exotic" syntax — the former is syntactic sugar for a very predictable value class (well, a "record"), while the latter contains the constructor arguments up in the class declaration header while still having an explicit body (where the constructor parameters are in scope). |
Proposed syntax:
(Here's the Perl 6 class for comparison:)
A couple of things of note:
__init__
method. You're not expected to need one.self
, there's nothis
. The variables declared withmy
in the class block happen to be lexically available in the methods, but that's all.There's no class inheritance.setProp
(orbutWith
or whatever we end up calling it).my
orsub
inside the class block is conservatively disallowed.BEGIN
block instead. On similar grounds,BEGIN
blocks are out.constant
declarations might be OK, though. They'd be a kind of once-only variables, valid throughout the class. If we wanted to be fancy, we could even allowMyClass.someConstant
to resolve. But I doubt we'd want to be that fancy.Q::Statement::My
toQ.Statement.My
.) Eventually decided against it because it feels like it's confusing together two very different things: the closed declaration of a class and its attributes/methods, and the open-ended declaration of namespaces.The syntax for creating an object mirrors object literals quite a lot.
(In this case, we could've skipped passing
assignment
, as it's already optional.We could have used the
new
keyword in the construction syntax, but I don't feel that it adds anything.As a nice retrofit, writing
Object { ... }
also works, and means the same as an ordinary object literal. Unlike user-defined classes, theObject
type doesn't check for required or superfluous properties.Tuple classes
After we get types, I'd very much like for us to get a "tuple class" syntax, where you don't name your attributes, but only specify their types as a tuple:
And the corresponding constructor syntax would look like a function call:
Internally, the attributes of a tuple class could have 0-based indices instead of names, and so you could still index them with
getProp
et al. This type of class would really shine if we had ADTs and case matching, the issue which see.Meta-object protocol
If you ask
type(Q::Statement::My)
, you get back the answerType
. (Much like Python.) Here's a rough attempt to getType
to "close the loop", meta-object-wise. Using the conjectural/future type syntax.The text was updated successfully, but these errors were encountered: