Skip to content

Implement class declarations #32

@masak

Description

@masak

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;
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions