Intermediate struct extensions implementation

Hey everyone, this is a spinoff/tangent of the Struct Extensions Proposal thread, to talk about an intermediate short-term variant of the feature that I’m starting implementing now, that won’t cut off any of our eventual decisions in that thread.

This is a behind-the-scenes peek at how I’m interleaving implementation and design for this one, just for transparency and because talking implementation is fun. No final decisions are made here, and this temporary implementation doesn’t affect anything about the final design of struct extensions (if it seems like it is, I’ll back off implementation and rethink).

In other words, I have two goals I’m working on:

* Short-term (this thread): Figure out a “conservative min-viable-product” that I can start implementing now, which doesn’t cut off any long-term options.

* Long-term (main proposal thread): Figure out the final design.

Anyway, this is my interpretation of a conservative min-viable-product that I can start implementing:

* Decision 1 (“Where can conforming extensions be?”): I think option B (“Either in the struct’s file or the trait’s file.”) is good for now and we can consider separately whether to expand that to “the struct’s package or the trait’s package” or go even further.

* Decision 2 (“Should we allow non-conforming extensions?”): Option C (“Conservatively start with option B, reevaluate later.”) lets do that.

* Decision 3 (“Where can non-conforming extensions be?”) would be moot for now if we do Decision 2 Option C.

* Decision 4 (“What can be in an extension?”) proposal remains the same

* Decision 5 (“Handling conflicts”) proposal remains the same

* Decision 6 (“Are extensions automatically imported?”) conservative option is actually Option A, will do that for now.

* Decision 7 (“Need import extension?”) remains the same, don’t want it.

* Decision 8 (“What if we import only an extension but not its target struct?”) is moot now because importing it is explicit.

* Decision 9 (“Support importing multiple extensions?”) remains the same, yes.

* Decision 10 (“Proposed Syntax”). Syntax is easy to change. I might call it `__extension` for now though, so people don’t think it’s the final syntax.

* Decision 11 (new, “What decorators can be on extensions’ methods”) I’ll conservatively disallow all decorators for now.

If anyone has any questions or thoughts on this temporary implementation, let me know in this thread!

I love your idea to implement an MVP with a conscious decision to avoid locking in the unresolved design decisions. It would be good to maintain an official list of these unresolved decisions, so that we don’t forget that a decision was unresolved and later on we say “oh, it’s too late to change this now”.

I agree with all of the placeholder decisions for this MVP except for decision 2. I really don’t understand the motivation for forbidding an extension that adds an ordinary method to an existing type:

extension String:
    fn trimmed(self) -> Self:

processed_strings = [x.upper().trimmed() for x in strings]

Extension methods are useful for all of the same reasons that regular methods are useful. I can’t see why we would forbid them, given they arise naturally from the proposed design.

In my opinion, Mojo extensions would be simpler if the above example was supported, because it would mean that an extension block looks and feels more similar to a struct block. (The main difference is that you can’t define vars.)

TL;DR: Surely we can resolve decision 2 now, rather than leaving it for a future round of discussion?

2 Likes

Very strong agree with @Nick :slight_smile:

Given that there won’t be non-conforming traits in this MVP, and the decision to start with file-scoped things for now, what does extensions provide for new capabilities until requires is available?

My concern is that problems or pain points with the MVP may not be discovered if it sees little use due to not providing users new capabilities.

I understand that from a compiler perspective same file no non-conforming extensions is a good MVP, but from a “we want people to stress test this language feature” perspective I’m not sure that it’s quite enough.

1 Like

Great work!
Just had this idea:

extension my_rounding[T:Floatable]:
        struct T:
                fn round(self:T)->Int:
                        ...

my_rounding[Float64].extend()
my_rounding[Float32].extend()

Great questions.

@Nick I agree with your leanings, but the conservative approach (from a language design perspective) is to err on the side of not allowing things. From an implementation perspective, it’s one easy if-statement to disallow it. Not a big problem. I don’t think we can resolve #2 today, because opinions are split; Connor and Stef have some legitimate concerns. I intend to keep pushing that other thread forward so we can have the full design planned out within a few weeks, so I’m not worried about forgetting unresolved decisions.

@owenhilyard good thought. I hadn’t actually thought about others trying it out, really just my integration tests for now. I’ll think more about when to advertise that it’s ready to try out.

@rd4com I have no idea what’s going on there, but it feels like sorcery and I want to know more, lol. Bring it up on the other thread? (with explanations!)

2 Likes