fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
345 stars 21 forks source link

Allow Type Augmentation Extension on composite types #858

Open Swoorup opened 4 years ago

Swoorup commented 4 years ago

I propose we allow adding intrinsic/optional extensions on type augmentation or composite data types. This makes it a bit more natural to extend composite types analogous to using intrinsic/optional extension on single data type.

Will stress that this is coming purely to make writing type extensions more natural and doesn't care much about interoperability with C#. Type aliases would also simply expand to the underlying type and create extension methods.

type int list with 
    member this.Print() = 
        printfn "%A" this

The existing way of approaching this problem in F# is using Extension methods:

[<Extension>]
type IntExtensions = 
    [<Extension>]
    static member Print(_this: int list) = 
        printfn "%A" _this

Pros and Cons

The advantages of making this adjustment to F# are able

The disadvantages of making this adjustment to F# are

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M-XL

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

Please tick all that apply:

For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

Happypig375 commented 4 years ago

These two compile differently. One is C#-interoperable and can only extend methods, while the other is F#-specific and can extend anything about the type.

Swoorup commented 4 years ago

Sure, my point is to allow writing as such regardless of the implementation detail. Looking at the de-compilation, I don't really the harm in doing so.

https://sharplab.io/#v2:DYLgZgzgNAJiDUAfAtgexgV2AUwAQFlcBYAKFNQAdsA7XAZQE8IAXbZAOgCUNrmBLZNnYBhVMgp8cAJzrYpANz4BjbBHJVajFm1K6SAbQA8AUQAerahD6pqAPgC6pZgyq4AkrzMWrNiLgC8xCS4IbhGXjQ+do7BoSwAhvxKuILIAEZyuAAKUny8ABQA+swAFnwQIO68AMwATACUAbgUubxgtABEAKQAgh24xWVqsaGhpEHOrh7MdbgA7nylQaOpGVK4peXsOXnM+Y2BLbvtuN19G0NAA

Happypig375 commented 4 years ago

You do know that the F# compiler generates an opaque resource blob that is not visible in decompilation, right?

Swoorup commented 4 years ago

Sure, maybe just generate a opaque blob then and not allow interoping from C# ?

cartermp commented 4 years ago

@Swoorup Can you please follow the issue template for future suggestions? Thanks.

I'm not sure how to rationalize this with extended properties like this:

type Int32 with 
    member this.Zero = 0

Properties like this can't be emitted the same way that extension methods can, and can't be consumed by C# or VB via that mechanism either. We also don't allow attributes on type extensions, further complicating the problem.

I think the first thing to do here would be to start with https://github.com/fsharp/fslang-suggestions/issues/835 to clean up the current requirement to make a class-level decoration. We could then figure out how best to square that behavior with existing type extensions. Though I'm sure this was certainly discussed to some degree when support for extension methods was added to F#.

munael commented 4 years ago

It does feel like a leak of implementation detail.

Why can't the natural syntax emit C#-interoperable code for methods, and F# specific code for everything else?

dsyme commented 4 years ago

I propose we allow adding extensions on type augmentation.

I'd be totally fine with allowing the declarations, though when I looked at it in F# 3.x it was frustratingly difficult to make this practical, and I believe there were some cases which it wasn't going to cover

IIRC the C#-style methods allow things that would need declarations like this:

type 'T when 'T :> IComparable with 
    member ....

that is the this parameter is a constrained variable type.

While not impossible it just brought difficulties in the implementation.

On interoperability: Getting an interoperable form to drop out was also difficult, and would have required a breaking change unfortunately.

abelbraaksma commented 4 years ago

@dsyme, interestingly in some specific cases it appears that it already works (courtesy of Zaid Ajaj in Slack):

> type 'a ``[]`` with
    member instance.DoStuff() = 42;;
type 'T [] with
  member DoStuff : unit -> int

> [|12|].DoStuff();;
val it : int = 42

I.e., this creates a type extension on a generic type 'T [], which is somewhat similar to what is suggested here. Note that it is currently not possible to use type 'a with or type 'a[] with, which would be the "normal" way to code this, were this allowed.

~While this clearly is a bug~ (edit: not a bug, see follow-up), it suggests that the compiler apparently has a way of dealing with some sort of composite types.

kevmal commented 4 years ago

@abelbraaksma This is by design, outlined in §8.12 of the F# spec

abelbraaksma commented 4 years ago

@kevmal, thanks! I've always thought double backticks were used for random names with spaces and other forbidden characters, this is the first time I encounter them as a language mechanism. That section also shows that type 'a List with etc is allowed. Not a bug at all then :). Interesting :+1:, I don't remember to have ever used this syntax.

image

Swoorup commented 4 years ago

Updated top-level description to match the template. @cartermp

dsyme commented 3 years ago

I'm marking this as approved in principle though from past experience it's hard to make actual progress on it.