-
Notifications
You must be signed in to change notification settings - Fork 6k
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
Rewrite type extensions #6462
Changes from 2 commits
f4f1d93
dc4acb2
d7e01d6
f84c8ad
b5cbb24
56e0e4a
e88c6f0
8815389
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,99 +1,159 @@ | ||
--- | ||
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 | ||
|
||
Type extensions let you add new members to a previously defined object type. | ||
Type extensions (also called _augmentations_) are a family of features that let you add new members to a previously defined object type. The three features are: | ||
|
||
* Intrinsic type extensions | ||
* Optional type extensions | ||
* Extension methods | ||
|
||
Each can be used in different scenarios and has different tradeoffs. | ||
|
||
## Syntax | ||
|
||
```fsharp | ||
// Intrinsic extension. | ||
// Intrinsic and optional extensions | ||
type typename with | ||
member self-identifier.member-name = | ||
body | ||
... | ||
[ end ] | ||
|
||
// Optional extension. | ||
type typename with | ||
member self-identifier.member-name = | ||
// Extension methods | ||
open System.Runtime.CompilerServices | ||
|
||
// On a module | ||
[<Extension>] | ||
module TypeNameExtensions = | ||
let [inline] extension-name (ty: typename) [args] = | ||
body | ||
... | ||
|
||
// On a class | ||
[<Extension>] | ||
type Extensions() = | ||
[static] member self-identifier.extension-name (ty: typename, [args]) = | ||
body | ||
... | ||
[ end ] | ||
``` | ||
|
||
## Remarks | ||
There are two forms of type extensions that have slightly different syntax and behavior. An *intrinsic 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. An *optional extension* is an extension that appears outside the original module, namespace, or assembly of the type being extended. Intrinsic extensions appear on the type when the type is examined by reflection, but optional extensions do not. Optional extensions must be in modules, and they are only in scope when the module that contains the extension is open. | ||
## Intrinsic type extensions | ||
|
||
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, or in the same assembly (DLL or executable file) as the type being extended. | ||
|
||
The `end` keyword is optional in lightweight syntax. | ||
Intrinsic type extensions are sometimes a cleaner way to separate functionality from the type declaration. For example: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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..." |
||
|
||
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. | ||
```fsharp | ||
namespace Example | ||
|
||
type Variant = | ||
| Num of int | ||
| Str of string | ||
|
||
module Variant = | ||
let print v = | ||
match v with | ||
| Num n -> printf "Num %d" n | ||
| Str s -> printf "Str %s" s | ||
|
||
// Add a member to Variant as an extension | ||
type Variant with | ||
member x.Print() = Variant.print x | ||
``` | ||
|
||
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. | ||
This 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 were a member on the type. This is an alternative to defining everything as a member on `Variant`. This is not necessarily preferable to do, but it is an option if you prefer to cleanly separate things. | ||
|
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. members on the type -> members of the type? |
||
|
||
In the following example, a type in a module has an intrinsic type extension. To client code outside the module, the type extension appears as a regular member of the type in all respects. | ||
## Optional type extensions | ||
|
||
[!code-fsharp[Main](../../../samples/snippets/fsharp/lang-ref-2/snippet3701.fs)] | ||
An optional type extension is an extension that appears outside the original module, namespace, or assembly of the type being extended. | ||
|
||
You can use intrinsic type extensions to separate the definition of a type into sections. This can be useful in managing large type definitions, for example, to keep compiler-generated code and authored code separate or to group together code created by different people or associated with different functionality. | ||
Optional type extensions are useful for extending a type that you have not defined yourself. For example: | ||
|
||
In the following example, an optional type extension extends the `System.Int32` type with an extension method `FromString` that calls the static member `Parse`. The `testFromString` method demonstrates that the new member is called just like any instance member. | ||
```fsharp | ||
module Extensions | ||
|
||
[!code-fsharp[Main](../../../samples/snippets/fsharp/lang-ref-2/snippet3702.fs)] | ||
open System.Collections.Generic | ||
|
||
The new instance member will appear like any other method of the `Int32` type in IntelliSense, but only when the module that contains the extension is open or otherwise in scope. | ||
type IEnumerable<'T> with | ||
/// Repeat each element of the sequence n times | ||
member xs.RepeatElements(n: int) = | ||
seq { | ||
for x in xs do | ||
for i in 1 .. n do | ||
yield x | ||
} | ||
``` | ||
|
||
## 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 were 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. | ||
|
||
For example, in F# 3.1 code, you can use extension methods with signatures that resemble the following syntax in C#: | ||
Optional extensions do not appear on the extended type when examined by reflection. Optional extensions must be in modules, and they are only in scope when the module that contains the extension is open or is otherwise in scope. | ||
|
||
```csharp | ||
static member Method<T>(this T input, T other) | ||
``` | ||
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. | ||
|
||
## 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, |
||
|
||
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: | ||
However, even when constraints are matched between a declared type and a type extension, it is possible for a constraint to be inferred by the body of an extended member that enforces a further constraint that will lead to a type error: | ||
|
||
```fsharp | ||
open System.Collections.Generic | ||
|
||
// NOT POSSIBLE AND FAILS TO COMPILE! | ||
type IEnumerable<'T> with | ||
/// Repeat each element of the sequence n times | ||
member xs.RepeatElements(n: int) = | ||
seq { for x in xs do for i in 1 .. n do yield x } | ||
member this.Sum() = Seq.sum this | ||
``` | ||
|
||
However, for a generic type, the type variable may not be constrained. You can now declare a C#-style extension member in F# to work around this limitation. When you combine this kind of declaration with the inline feature of F#, you can present generic algorithms as extension members. | ||
To get around this restriction, you can use extension methods. | ||
|
||
## Extension methods | ||
|
||
Finally, extension methods (sometimes called "C# style extension members") can be declared in F# as either a let-bound value value in a module or a member on a class. | ||
|
||
Consider the following declaration: | ||
Extension methods are useful for when you wish to define extensions on a generic type that will constrain the type variable. For example: | ||
|
||
```fsharp | ||
namespace Extensions | ||
|
||
open System.Runtime.CompilerServices | ||
|
||
[<Extension>] | ||
type ExtraCSharpStyleExtensionMethodsInFSharp () = | ||
module IEnumerableExtensions = | ||
[<Extension>] | ||
static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs | ||
let 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. encode the `1 to be %601 so long as -> as long as |
||
|
||
```fsharp | ||
let listOfIntegers = [ 1 .. 100 ] | ||
let listOfBigIntegers = [ 1I to 100I ] | ||
let sum1 = listOfIntegers.Sum() | ||
let sum2 = listOfBigIntegers.Sum() | ||
``` | ||
## Other remarks | ||
|
||
In this code, the same generic arithmetic code is applied to lists of two types without overloading, by defining a single extension member. | ||
Type extensions also have the following attributes: | ||
|
||
* Any type that can be accessed can be extended. | ||
* Intrinsic and optional type extensions can define _any_ member type, not just methods. So extension properties are also possible, for example. | ||
* The `self-identifier` token in the [syntax](type-extensions.md#syntax) represents the instance of the type being invoked, just like ordinary members. | ||
* Extended members can be static or instance members. | ||
* Type variables on a type extension must match the constraints of the declared type. | ||
|
||
The following limitations also exist for type extensions: | ||
|
||
* Type extensions cannot be virtual or abstract methods. | ||
* Type extensions cannot be defined on [type abbreviations](type-abbreviations.md). | ||
* Type extensions do not support override methods as augmentations. | ||
* Type extensions do not permit constructors as augmentations. | ||
* Type extensions are not valid for `byref<'T>` (though they can be declared). | ||
* Type extensions are not valid for attributes (though they can be declared). | ||
* 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
Finally, 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. | ||
|
||
## See Also | ||
|
||
[F# Language Reference](index.md) | ||
|
||
[Members](members/index.md) |
There was a problem hiding this comment.
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