Skip to content
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

[Proposal]: Re-rename to Roles and Extensions #8431

Open
1 of 4 tasks
MadsTorgersen opened this issue Sep 13, 2024 · 8 comments
Open
1 of 4 tasks

[Proposal]: Re-rename to Roles and Extensions #8431

MadsTorgersen opened this issue Sep 13, 2024 · 8 comments
Assignees

Comments

@MadsTorgersen
Copy link
Contributor

MadsTorgersen commented Sep 13, 2024

Re-rename to Roles and Extensions

  • Proposed
  • Prototype: Not Started
  • Implementation: Not Started
  • Specification: Not Started

Summary

Rename explicit extension back to role and implicit extension back to simply extension.

Motivation

The proposed Extensions feature design eliminates the previously proposed distinction between "roles" and "extensions", in recognition that they are in fact two flavors of the same feature. Instead it introduces modifiers explicit and implicit onto the shared declaration keyword extension.

This makes sense from a language designer economy-of-concepts point of view. However, the intended mainline use of the two kinds of extension differs considerably: One is explicitly used as a type, whereas the other implicitly adds members to an existing type. The overlap in intended usage is small, boiling down to using an implicit extension explicitly as a type for disambiguation purposes.

We've now had some time to get experience with the design. In practice, anyone but a language designer or implementer rarely has to discuss the overall feature as a whole. Any given declaration is either an "explicit extension" or an "implicit extension", and the intended mode of use follows from that. The terms are not only long but deceptively similar, causing confusion as well as verbosity.

We should go back to clear, separate nouns for the two features. The previously used nouns "role" and "extension" are great candidates, though the choice of terms is less important than the choice of having two of them. This will help people understand them separately, in a manner specific to their main usage patterns. It allows developers to adopt them one at a time, and not worry much about the connections between them.

Going back to a narrower use of the term "extension" also keeps it better aligned with the meaning developers are already used to from extension methods. There have been demands for "extension members" and "extension everything" ever since extension methods were added, and this use of the term matches the intuition reflected in these asks.

This is not a proposal to turn roles and extensions into completely separate features. With the rename an extension will still also be a role. The two features benefit hugely from having a shared core. The syntax of declarations, the type of this inside them, the conversions to and from the underlying type, the code generation strategies, etc., etc., all entirely overlap. Additionally, envisioned future feature evolution such as inheritance and interface implementation apply to and have compelling scenarios for both features.

It is conceivable that this step opens up opportunities where more differences between roles and extensions would be possible and beneficial. That is totally fine but is not implied here.

Detailed design

In the proposal grammar change the extension_declaration production as follows:

role_declaration
    : role_modifier* ('role' | 'extension') identifier type_parameter_list? ('for' type)? type_parameter_constraints_clause* role_body
    ;

Rename all productions named extension_NNN to role_NNN. Throughout the proposal, update code snippets accordingly.

In prose, change "implicit extension" to "extension" and unqualified "extension" to "role". There are only two occurrences of "explicit extension" in prose; they can easily be rewritten (to e.g. "non-extension role"), and "role" can safely be used as the overarching term. It turns out that it is rare for the specification to have to talk about explicit extensions only, since everything that applies to them applies to implicit extensions also.

In the Implementation Details section we will want to adjust the naming of emitted attributes and members, and there are probably a few other places that would benefit from a rewording after this change.

Examples

// Roles intended to be used directly as types
public role Order for JsonElement
{
    public string Description => GetProperty("description").GetString()!;
}

public role Customer for JsonElement
{
    public string Name => GetProperty("name").GetString()!;
    public IEnumerable<Order> Orders => GetProperty("orders").EnumerateArray();
}

// Extensions intended to provide new function members to existing types
public extension JsonString for string
{
    private static readonly JsonSerializerOptions s_indentedOptions = new() { WriteIndented = true };

    public JsonElement ParseAsJson() => JsonDocument.Parse(this).RootElement;

    public static string CreateIndented(JsonElement element)
        => element.ValueKind != JsonValueKind.Undefined
            ? JsonSerializer.Serialize(element, s_indentedOptions)
            : string.Empty;
}

Drawbacks

It becomes less directly clear from syntax that extensions are just roles with more behavior.

Alternatives

Alternative nouns

While "extension" is very likely the right term for "implicit extensions" because of the connection to the existing extension methods, there is more room for debate about "role". While that term does occur in literature, it is not particularly established among developers. We can essentially pick whichever term we like, and many others have been proposed.

Allowing extension role

If we want the language to more directly reflect that every extension is a role, we could make the declaration syntax for extensions allow an optional role keyword to occur in the declaration:

public extension role JsonString for string { ... }

// equivalent to

public extension JsonString for string { ... }

This would be somewhat similar to record class which is allowed to be abbreviated to record. However, for records the use of record class might be motivated by wanting to highlight that it is not a record struct. For extension role on the other hand there is no extension something-else to distinguish it from.

We don't have great scenarios at the moment (outside disambiguation) where an extension is also intended to be used as a role (i.e. an explicit type), but such scenarios may come up. In those cases, it might be useful to stress this intent by explicitly putting the word role in the declaration, even if it doesn't have semantic impact.

This is something that could be added anytime. We could ship the feature without it, and add the ability to say extension role as a long form of extension add any future point if it seems warranted. By analogy we also shipped record first and then retcon'ed it to be an abbreviation of record class in a subsequent release when record struct was added.

Implementing extensions before roles

Using separate nouns as proposed here, it may become an option to ship extensions first and roles later. While this-access and disambiguation for extensions use them as a named type, that is only going to be needed locally in a function body. By contrast, roles are likely to be used in signatures, and so require quite an elaborate metadata scheme for implementation.

Perhaps there's a stage where we leave out role declarations, as well as disallow extension types from occurring in signatures. The latter restriction would be similar to the one that applies to anonymous types in today's C#.

Unresolved questions

Design meetings

https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-18.md#extensions-naming

@colejohnson66
Copy link

colejohnson66 commented Sep 13, 2024

I get the argument, and I could agree it’s better than “implicit/explicit” extension, but I can’t shake the feeling that “role” is weird. Because an explicit extension/role is still conceptually an extension of the specified type, just one you only use in specific situations - a proxy type of sorts. I don’t know; Just my $0.02.

@Sergio0694
Copy link

Just my 2 cents: if we want to drop implicit/explicit, would "extension type" not be an option for roles? I agree that "role" sounds weird. To me an explicit extension (a role) is really just a type. You treat it exactly as if it were an actual type in code (you use it to declare parameters, locals, can cast to it, etc.). It's just that it's specifically also an extension, not a "real" type.

So to me "extension type" seems intuitive in that sense, more than "role". With the latter you get into "what is a role", "what does a role mean", etc., whereas with the former, people will just look at it as "oh ok, it's just a type I can declare an use, cool".

@KathleenDollard
Copy link
Collaborator

I suggest we use "extension types" instead of "extensions" for implicit extensions. I think we will want to distinguish these from extension methods and also discuss the two in combination.

@HaloFour
Copy link
Contributor

One thing I kind of liked about the implicit vs explicit extension types is that it conveyed that one was widening while the other was narrowing, in that explicit extensions might not always apply to the underlying type and that some type of possibly fallible validation/conversion was required. I think that's an important feature for the type and I don't think role conveys that concept at all.

@KennethHoff
Copy link

With the name explicit extensions the fact that this scenario did not work made some amount of sense; "it's just an extension to the underlying type, of course it doesn't act any differently", but if we go for a name like "role" it implies that it should be more than just "extension to some underlying type", so I hope this use-case gets re-considered.

@FaustVX
Copy link

FaustVX commented Sep 16, 2024

One thing I kind of liked about the implicit vs explicit extension types is that it conveyed that one was widening while the other was narrowing, in that explicit extensions might not always apply to the underlying type and that some type of possibly fallible validation/conversion was required. I think that's an important feature for the type and I don't think role conveys that concept at all.

To me, it's the other way. extension doesn't mean there will be some validation to be required. role sound better; ie: not every people can have a role of any job, you have to qualify to apply.

@timcassell
Copy link

With the name explicit extensions the fact that this scenario did not work made some amount of sense; "it's just an extension to the underlying type, of course it doesn't act any differently", but if we go for a name like "role" it implies that it should be more than just "extension to some underlying type", so I hope this use-case gets re-considered.

I imagine C# could fake it by emitting an unspeakable function name to IL and mapping it to the declared name for source code. Though it would be better if the CLR would add extension/role types to the type system.

@Thaina
Copy link

Thaina commented Oct 3, 2024

Just happen to see this was changed into role so pardon me for duplicate

I am not so sure about current use case of role but I have an idea I wish it could support

I think role should have conditional checking for casting as is. If it fail the condition it should be null

Suppose we have

public class Person
{
    public DateTimeOffset birthDay;
}

public extension PersonExt for Person
{
    public TimeSpan Age => DateTimeOffset.Now - birthDay; // everyone has age
}

public role Adult for Person // not everyone is adult
{
    public static bool operator is(Person person) => person.Age.TotalYears >= 18;
    public bool HasJob => false;
    public bool Occupation => "College Student";
}

////

Person person0 = LoadFromDB(pid0);

if(person0 is Adult adult0) // check with operator is
    Console.WriteLine(adult0.Occupation);

var adult1 = (Adult)LoadFromDB(pid1) // null if operator is return false

Alternative syntax might be when and expression block

public role Adult for Person when (Age.TotalYears >= 18)
{
    public bool HasJob => false;
    public bool Occupation => "College Student";
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants