masak / alma

ALgoloid with MAcros -- a language with Algol-family syntax where macros take center stage
Artistic License 2.0
137 stars 14 forks source link

What happens when we subclass Type? #266

Open masak opened 6 years ago

masak commented 6 years ago

Subclassing is quite straightforward in the post-#242 world: you just do _007::Type.new(:name<NewType>, :base($basetype)).

But what exactly happens when $basetype is TYPE<Type>? I'm asking because I don't exactly know.

There are at least two parts to this question:

vendethiel commented 6 years ago

I'm too tired to think about all the implications, but it sounds like this could be very interesting, especially WRT what a 007 MOP could do.

masak commented 6 years ago

I'd like to playfully nickname this issue The Curse of Girard.

masak commented 6 years ago

Here's one thing that would be desirable to be able to do with subtyping of Type:

As far as I can see, the straightforward implementation of #265 is just something like this:

my $enum = _007::Type(:$name, :fields["name"]);
declare-in-scope($name, $enum);    # *handwave*
for @enum-values -> $v {
    declare-in-scope($v, create($enum, :name($v)));
}

(I just re-aligned the #242 branch so that there's no longer an internal _007::Object::Enum type. That is, enum values such as None and True play under exactly the same rules as ordinary types and values in 007.)

Now one problem is that someone could instantiate a new enum value from the enum type. This is unfortunate, because runtime instantiation of enum values ultimately degrades the usefulness of enum types. (Java and Python certainly forbid instantiating enums. Perl 6, surprisingly, allows this.)

Since MyEnum.create(...) would be how you create new enum values, I'd like to override the .create method on MyEnum. Since we don't override methods per-instance in 007 (yet, at least), the way I'd have to do that is by subclassing Type into, hm, EnumType and providing it with a new create method. One that just dies when you call it.

Another problem is if someone tries to subtype MyEnum. I'm not sure being able to do this is on par with being able to instantiate it — especially if you can't create instances of the subtype either, but I'm not sure offhand we can enforce that... *brain explodes* — but let's assume it is. The problem now is that subtyping MyEnum doesn't happen as a method call on MyEnum, it happens as Type.create (or EnumType.create — which, note, is distinct from the overridden MyEnum.create). We could override EnumType.create too, I think, by leveling up and creating an EnumTypeType. I'm not 100% sure I like that.

Better perhaps to find a way to affix an @Unsubclassable trait (Java would call it @Final) to the EnumType class. And then have the built-in create respect this when checking the :base parameter.

I need to stop writing now, because my head is spinning from walking up and down the spiral staircase of type levels.

masak commented 6 years ago

Thinking about this some more, I realized that in order for us to effectively have types with custom types, _007::Type needs to have a $.type attribute. Another way to say this is that "the type of a type object is (exactly) Type" is an oversimplification — it's enough to type-match with Type, i.e. it can be a subclass.

In retrospect, that's fairly obvious, and I suspect I'd've come to that realization through some other route towards the end of #242. It falls out of Type needing to be, in all aspects, a subclass of Object — which has a type property.

I already implemented this in 5dc3031f54c73219241f92a942efe56eefd87e4c and 7184e0854114a6d9ff628c218dab8f53a5256ba7. I've yet to write tests for it, though.

(And it doesn't stop there. _007::Type will need to have %.properties and method definitions just like _007::Object. Certainly merits writing a few tests.)

masak commented 6 years ago

Thinking about this some more, I realized that in order for us to effectively have types with custom types, _007::Type needs to have a $.type attribute.

[...] (And it doesn't stop there. _007::Type will need to have %.properties and method definitions just like _007::Object. [...])

And, since they will share so much structure, maybe _007::Type shouldn't exist separately at all? The main reason it does right now is that this gives us a quick way on the Perl 6 level to check whether some object is a Type object. But there are other ways to do that.

masak commented 6 years ago

Interestingly, Smalltalk doesn't have a concept of static methods, it implements methods on the type/meta objects, much like I'd hope we could do in 007. Also quite similar to method ^foo in Perl 6, although in Perl 6, type objects and meta objects are two distinct things.

masak commented 6 years ago

the way I'd have to do that is by subclassing Type into, hm, EnumType

I think elsewhere in the issues I've called that new MOP type Type::Enum. Maybe that's a more stylish name.

Better perhaps to find a way to affix an @Unsubclassable trait (Java would call it @Final) to the EnumType class. And then have the built-in create respect this when checking the :base parameter.

When I thought about this the other day, it seemed to be the reasonable thing to do. The subtyping relationship is kind of reversed to how we'd want it — the base type is oblivious to its derived types — and so in that case, enforcing that something can not be subclassed needs to be left up to the OOP system itself.