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

Rewrite type extensions #6462

Merged
merged 8 commits into from
Jul 20, 2018
Merged

Rewrite type extensions #6462

merged 8 commits into from
Jul 20, 2018

Conversation

cartermp
Copy link
Contributor

This rewrites the article in an attempt to more clearly distinguish between the different kinds of type extensions.

@NinoFloris and @drvink, your feedback is welcome based on our chat in slack 😄

Copy link

@NinoFloris NinoFloris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments, others will follow tomorrow, if any 🙃


This approach is particularly useful when the generic type parameter is constrained. Further, you can now declare extension members like this in F# code and define an additional, semantically rich set of extension methods. In F#, you usually define extension members as the following example shows:
It is not possible to define an optional extension on a generic type when the type variable is constrained. For example, the following is not possible:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is actually possible, and I use it a lot. only apparently has some restrictions for the static member extension declarations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it's not just for static member extension declarations:

type C<'T when 'T : null>() =
    member __.Data =
        Array.zeroCreate<'T> 12
        |> Seq.ofArray

type C<'T> with
    member this.Bananas() = Seq.sum this.Data

This gives:

This code is not sufficiently generic. The type variable  ^T when  ^T : null and  ^T : (static member get_Zero : ->  ^T) and  ^T : (static member ( + ) :  ^T *  ^T ->  ^T) could not be generalized because it would escape its scope.

Which is sort of what I'd expect.

Also, if you do not match the type constraints of the declaration and type extension, then you get a warning:

One or more of the declared type parameters for this type extension have a missing or wrong type constraint not matching the original type constraints on 'C<_>'

So the rules seem to be making sure that you match constraints with the declared type and that you don't write code that ... confuses the type checker?


In the previous syntax, *typename* represents the type that is being extended. Any type that can be accessed can be extended, but the type name must be an actual type name, not a type abbreviation. You can define multiple members in one type extension. The *self-identifier* represents the instance of the object being invoked, just as in ordinary members.
An intrinsic type extension is an extension that appears in the same namespace or module, in the same source file, and in the same assembly (DLL or executable file) as the type being extended.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same source file implies same Assembly no? Less enumeration helps get the fact it's not just namespace or module but also same source file, so no parity with C# partial classes there. That's really important to underline.

Might even rewrite to something like "An intrinsic extension will only be considered intrinsic if it appears in the same source file and same scope, be that a namespace or module."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the and is supposed to be or - this is an old document, so I unfortunately don't know the history.


* Type extensions cannot be virtual or abstract methods.
* Type extensions cannot be defined on [type abbreviations](type-abbreviations.md).
* You can define extensions that overload other methods of the same name, but the F# compiler gives preference to non-extension methods in the case of an ambiguous call.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Byref 'this' is not possible due to self identifier rigidity, no attributes possible etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, byref is not possible (I've tried, unsuccessfully) - will call that out

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or rather, you can definitely declare one like this:

type byref<'T> with
    member __.Boof() = ()

But usage doesn't seem possible:

screen shot 2018-07-14 at 21 13 12

And fundamentally, the concept of extending a byref simply doesn't make sense. Adding arbitrary members to a pointer should simply not be possible. And in the extensive byref work we did to properly support span, I believe we are disallowing this concept altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, extending attributes seems fine as a declaration, though I wonder how you'd make any use of this:

screen shot 2018-07-14 at 21 46 41

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant a by reference 'this', such a feature is very useful for struct (and ref struct) extensions to skip a copy on invocation. VB always had it and C# with all the ref return/local perf features is considering following suit dotnet/csharplang#186

Copy link

@NinoFloris NinoFloris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some updates


* Type extensions cannot be virtual or abstract methods.
* Type extensions cannot be defined on [type abbreviations](type-abbreviations.md).
* You can define extensions that overload other methods of the same name, but the F# compiler gives preference to non-extension methods in the case of an ambiguous call.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant a by reference 'this', such a feature is very useful for struct (and ref struct) extensions to skip a copy on invocation. VB always had it and C# with all the ref return/local perf features is considering following suit dotnet/csharplang#186

It is not possible to define an optional extension on a generic type when the type variable is constrained. For example, the following is not possible:
## Generic limitation of intrinsic and optional type extensions

It is possible to declare a type extension on a generic type where the type variable is constrained. The requirement is that the constraint of the extension declaration matches the constraint of the declared type.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's what I wanted to add, what you could add though is that specifically SRTP parameters (constrained or not) are not allowed. (I just tested this, ^T already fails)

@drvink
Copy link
Contributor

drvink commented Jul 15, 2018

Looks great; I don't think I have anything to add, though I'll just say I particularly appreciate the discussion on limitations--how the different extension forms map to in-assembly representations based on which form is used and where it is located in a given file, as well as when the resulting extensions will be visible in a given name environment, is crucial to understanding these features, and they're explained much better here than in the previous version of the document.

@mairaw
Copy link
Contributor

mairaw commented Jul 17, 2018

Have you run Acrolinx on this one @cartermp?

@cartermp
Copy link
Contributor Author

@mairaw Just did!

Copy link
Contributor

@mairaw mairaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but left a few comments for you to consider

[<Extension>]
static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs
```

By using this declaration, you can write code that resembles the following sample.
When used, this code will make it appear as if `Sum` is defined on <xref:System.Collections.Generic.IEnumerable`1>, so long as `Extensions` has been opened or is in scope.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encode the `1 to be %601

so long as -> as long as


The `end` keyword is optional in lightweight syntax.
Intrinsic type extensions **must** be defined in the same file **and** namespace or module as the type they are extending. Any other definition will result in them being [optional type extensions](type-extensions.md#optional-type-extensions).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: they are -> they're (Microsoft's style recommends contractions)

others in this article:
it is -> it's
do not -> don't
etc.


The `end` keyword is optional in lightweight syntax.
Intrinsic type extensions **must** be defined in the same file **and** namespace or module as the type they are extending. Any other definition will result in them being [optional type extensions](type-extensions.md#optional-type-extensions).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add the word same after and?


Members defined in type extensions can be used just like other members on a class type. Like other members, they can be static or instance members. These methods are also known as *extension methods*; properties are known as *extension properties*, and so on. Optional extension members are compiled to static members for which the object instance is passed implicitly as the first parameter. However, they act as if they were instance members or static members according to how they are declared. Implicit extension members are included as members of the type and can be used without restriction.
Intrinsic type extensions are sometimes a cleaner way to separate functionality from the type declaration. For example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this for example here seems a bit lost. Maybe say something like "The following example shows..."


Extension methods cannot be virtual or abstract methods. They can overload other methods of the same name, but the compiler gives preference to non-extension methods in the case of an ambiguous call.
Using a type extension allows you to separate the declaration of a `Variant` type, functionality to print such a class, and offer a way to access this functionality as if it is a member on the type. Using an intrinsic type extension is an alternative to defining everything as a member on `Variant`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sentence "functionality to print such a class" seems out of context here. Maybe it's missing some conjunctions or something to connect the sentence better?


If multiple intrinsic type extensions exist for one type, all members must be unique. For optional type extensions, members in different type extensions to the same type can have the same names. Ambiguity errors occur only if client code opens two different scopes that define the same member names.
Intrinsic type extensions are compiled as members on the type they augment, and appear on the type when the type is examined by reflection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

members on the type -> members of the type?


## Generic Extension Methods
Before F# 3.1, the F# compiler didn't support the use of C#-style extension methods with a generic type variable, array type, tuple type, or an F# function type as the "this" parameter. F# 3.1 supports the use of these extension members.
You can now access `RepeatElements` as if it is a member on <xref:System.Collections.Generic.IEnumerable`1> so long as the `Extensions` module is opened in the scope that you are working in.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encode the `1 to be %601

member on -> member of

so long as -> as long as

@@ -1,99 +1,162 @@
---
title: Type Extensions (F#)
description: Learn how F# type extensions allow you add new members to a previously defined object type.
ms.date: 05/16/2016
ms.date: 07/14/2018
---
# Type Extensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this and the See Also heading are the only ones not using sentence case

@cartermp cartermp merged commit dfe7126 into dotnet:master Jul 20, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants