Proposed syntax:
class Q::Statement::My {
my ident;
my assignment = None;
sub Str {
if assignment == None {
return "My" ~ children(ident);
}
return "My" ~ children(ident, assignment);
}
sub run(runtime) {
if assignment == None {
return;
}
assignment.eval(runtime);
}
}
(Here's the Perl 6 class for comparison:)
role Q::Statement::My does Q::Statement {
has $.ident;
has $.assignment;
method new($ident, $assignment = Empty) { self.bless(:$ident, :$assignment) }
method Str { "My" ~ children($.ident, |$.assignment) }
method run($runtime) {
return
unless $.assignment;
$.assignment.eval($runtime);
}
}
A couple of things of note:
- Much like Python, we re-use lexically declared variables and subs to create attributes and methods.
- Unlike Python, there's no
__init__ method. You're not expected to need one.
- Variables with an assignment on them are optional. Variables that are not assigned are mandatory.
- Perhaps most crucially, there is no instance context. There's no
self, there's no this. The variables declared with my in the class block happen to be lexically available in the methods, but that's all.
- As a consequence, there's no private state. You can't even emulate it by having the methods close over a lexical defined outside of the class. (Or you could, but it would be "static", not per instance.)
There's no class inheritance.
- Just as with arrays and object literals, everything's immutable from construction-time onwards. If you want to mutate something, you call
setProp (or butWith or whatever we end up calling it).
- Anything other than
my or sub inside the class block is conservatively disallowed.
- Any "ordinary statements" are disallowed because they don't further the function of a class declaration. Use a
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 allow MyClass.someConstant to resolve. But I doubt we'd want to be that fancy.
- I nearly wanted to allow other class declarations inside the class body. Felt it could help to establish name spaces. (And then we'd probably switch from
Q::Statement::My to Q.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.
- Finally, macros would have been really really cool to allow as methods. Unfortunately, macros run long before the late-bound method dispatch happens. Even if we had types and knew what class the macro was run on, we still wouldn't know what instance it was called on.
The syntax for creating an object mirrors object literals quite a lot.
my declaration = Q::Statement::My {
ident: Q::Identifier {
name: "foo"
},
assignment: None
};
(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, the Object 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:
class Q::Statement::Sub (Q::Identifier, Q::Parameters, Q::Block) { ... }
And the corresponding constructor syntax would look like a function call:
my function = Q::Statement::Sub(
Q::Identifier("fib"),
Q::Parameters(Q::Identifier("n")),
Q::Block(Q::Statements(...))
);
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 answer Type. (Much like Python.) Here's a rough attempt to get Type to "close the loop", meta-object-wise. Using the conjectural/future type syntax.
class Type {
my name: Str;
my tuplish: Int; # since we don't have Bool
my properties: Array<PropSpec>;
}
class PropSpec {
my name: Str | Int;
my type: Type;
}
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 withmyin the class block happen to be lexically available in the methods, but that's all.There's no class inheritance.setProp(orbutWithor whatever we end up calling it).myorsubinside the class block is conservatively disallowed.BEGINblock instead. On similar grounds,BEGINblocks are out.constantdeclarations 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.someConstantto resolve. But I doubt we'd want to be that fancy.Q::Statement::MytoQ.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
newkeyword 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, theObjecttype 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
getPropet 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 getTypeto "close the loop", meta-object-wise. Using the conjectural/future type syntax.