fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Allow interfaces to implement static members

Open gusty opened this issue 2 years ago • 19 comments

Fixes #15992 by allowing interface .. with for static members in interfaces. Here's a simple example of what this PR allows:

type IParent<'T> =
    static abstract GetOne : unit -> int

[<Interface>]
type IDerived =
    interface IParent<IDerived> with
        static member GetOne () = 1

gusty avatar Oct 29 '23 17:10 gusty

It seems that the same thing is not allowed for non-statics, i.e., the following code yields the same diagnostic:

open System
open System.Collections.Generic

type ISemigroup<'T> = abstract op_AdditionAddition : 'T * 'T -> 'T

[<Interface>]
type NonEmptySeq<'t> =
    inherit IEnumerable<'t>
    abstract member First: 't
    
    interface ISemigroup<NonEmptySeq<'t>> with
        member _.op_AdditionAddition (x: 't NonEmptySeq, y) =
            let unsafeOfSeq (seq: _ seq) =
                { new NonEmptySeq<_> with
                    member _.First = Seq.head seq
                    member _.GetEnumerator() = seq.GetEnumerator()
                    member _.GetEnumerator() = seq.GetEnumerator() :> Collections.IEnumerator }
            let append (source1: 'a NonEmptySeq) (source2: 'a NonEmptySeq) = Seq.append source1 source2 |> unsafeOfSeq
            append x y

So, this introduces a completely new construct it seems? It almost looks like it allows a default implementation for interfaces via another interface.

cc @T-Gro @dsyme

vzarytovskii avatar Oct 30 '23 10:10 vzarytovskii

Isn't this https://github.com/fsharp/fslang-suggestions/issues/679 , i.e. producing default interface methods?

T-Gro avatar Oct 30 '23 11:10 T-Gro

Isn't this fsharp/fslang-suggestions#679 , i.e. producing default interface methods?

Maybe related, but not exactly, it's providing implementation for interface member in another interface (not the default interface implementation feature). I don't think it was possible before, but not it will be.

vzarytovskii avatar Oct 30 '23 11:10 vzarytovskii

I am actually not entirely sure CLR will allow it for interfaces, without using defaults.

vzarytovskii avatar Oct 30 '23 11:10 vzarytovskii

It seems that the same thing is not allowed for non-statics, i.e.,

Yes, it is not allowed for non-statics, but for good reasons: interfaces can't have non-static members.

gusty avatar Oct 30 '23 12:10 gusty

It seems that the same thing is not allowed for non-statics, i.e.,

Yes, it is not allowed for non-statics, but for good reasons: interfaces can't have non-static members.

I don't think I understand this sentence or missing something:

type IOne = interface
    abstract Foo : unit -> unit
end

[<Interface>]
type ITwo =
    abstract Bar : unit -> unit

type OneTwo =
    interface IOne with
        member _.Foo() = ()
    interface ITwo with
        member _.Bar() = ()

These are non-static virtual abstract members in interfaces, with virtual implementations

(#lab)

vzarytovskii avatar Oct 31 '23 11:10 vzarytovskii

It seems that the same thing is not allowed for non-statics, i.e.,

Yes, it is not allowed for non-statics, but for good reasons: interfaces can't have non-static members.

I think I get what you mean - "interfaces can't have non-static non-abstract members".

vzarytovskii avatar Oct 31 '23 11:10 vzarytovskii

I think I get what you mean - "interfaces can't have non-static non-abstract members".

Exactly, but they can have static non-abstract, so they naturally should be able to use them to implement static abstracts. Also you might have noticed that the CLR supports this, as does C# as well.

Thanks for the feedback, I will try to find time to write the IL test and the RFC as soon as possible.

gusty avatar Oct 31 '23 14:10 gusty

@gusty Do you need any assistance with this PR?. Happy to help

edgarfgp avatar Apr 16 '24 16:04 edgarfgp

@gusty Do you need any assistance with this PR?. Happy to help

Thanks @edgarfgp as per comments what is needed is an RFC. If you want to write one that will be much appreciated. Then I can rebase it and submit it again.

gusty avatar Apr 16 '24 16:04 gusty

Isn't this fsharp/fslang-suggestions#679 , i.e. producing default interface methods?

Thinking about it again, it indeed allows an "implementation" (i.e. virtual methods) in interfaces. It's very close to default members, we need to be very careful what we're allowing here, and this definitely need to go through approval from Don.

vzarytovskii avatar Apr 17 '24 11:04 vzarytovskii

Isn't this fsharp/fslang-suggestions#679 , i.e. producing default interface methods?

Thinking about it again, it indeed allows an "implementation" (i.e. virtual methods) in interfaces. It's very close to default members, we need to be very careful what we're allowing here, and this definitely need to go through approval from Don.

@dsyme Can you please review this PR so I can help Gustavo finishing this.

edgarfgp avatar Apr 17 '24 15:04 edgarfgp

Yes we'd need an approved language suggestion for this, and couldn't accept it until there was one.

That said, @vzarytovskii is right in saying supporting authoring of IWSAMs with default functionality is pretty much excluded by https://github.com/fsharp/fslang-suggestions/issues/679 and I don't really expect that to change.

However that issue does technically predate IWSAMs and it could be that there are new viewpoints to be fielded not covered by that and other related issues.

I did look at 679 ad the use case mentioned by @Lanayx and didn't see that Pulsar.Client had actually needed to define IWSAMs in C# as a result of the lack of authoring in F#.

dsyme avatar Apr 17 '24 18:04 dsyme

Thanks @dsyme for taking the time.

I guess I can create a suggestion for this, but if this does not have an option to be accepted and to value everyone’s time I think is not worth it.

From my perspective and not knowing most the technical details, I’m just having a hard time finding why fsharp could not support this feature.

Anyway I would suggest to close this PR and update the related RFC if needed to make this really clear.

edgarfgp avatar Apr 17 '24 19:04 edgarfgp

Thanks @dsyme for taking the time.

I guess I can create a suggestion for this, but if this does not have an option to be accepted and to value everyone’s time I think is not worth it.

From my perspective and not knowing most the technical details, I’m just having a hard time finding why fsharp could not support this feature.

Anyway I would suggest to close this PR and update the related RFC if needed to make this really clear.

I suppose it's more about real world scenario(s), which would require that (especially taking into account latest changes w.r.t. static abstracts in interfaces). If there's need for that, with examples which can't be expressed in F#, we'll need to frame that exact feature and formally describe it in suggestion.

vzarytovskii avatar Apr 17 '24 19:04 vzarytovskii

Anyway I would suggest to close this PR and update the related RFC if needed to make this really clear.

Don't close it, from my viewpoint it's ready to merge, it has tests and everything's green. It's just a matter of deciding, as Don and Vlad already expressed, whether we want this functionality or not.

The RFC is work, and it requires a lot of thinking, also it should be contrasted with what C# offers right now, but I agree in that it's needed. It could be that in the end this is accepted but needing additional stuff, who knows.

I had some cases in mind when I did this PR, I will try to remember what was the problem I was trying to address.

gusty avatar Apr 17 '24 19:04 gusty

I’m just having a hard time finding why fsharp could not support this feature.

@edgarfgp The kind of composition and reuse this sort of feature encourages is at odds with the intended use of the F# language design, where composition is focused on functions and data, and sharing is mediated by explicit function parameters, and types keep things in shape (i.e. typed functional programming). In contrast this sort of mechanism is about composing types, abstracting over characteristics of types and sharing characteristics of types implicitly (i.e. type-level programming).

To put it another way, the only reason you'd define or implement a default implementation for a static abstract method on an interface is to use that interface as a constraint on a type parameter in generic code, which in turn implies you're defining and using a whole framework oriented around type-level generics, and using these methods as a form of "axiomatic" code sharing. This kind of programming is very hard to get right and quite implicit - it's really hard to know what implementation you're actually calling from your generic code. Some of the other drawbacks of this whole area is captured in these discussions here and here.

This is similar to the way "protected" is not supported in F# OOP. In practice "protected" (and implementation inheritance generally) is used to try to share fragments of implementations while controlling access. There are some good uses but implementation inheritance is a really hard mechanism to use well and 99% of the time is used very badly. F# draws a line in the sand and says "no" to "protected" to say "this is not the way you share things in F#, this is not the way you abstract, this is not the way you encapsulate, this is not the way you design". While that loses the some of the 1% of (very) rare good uses of "protected" in implementation inheritance, it keeps F# practice rooted in the simplicity of functions+data.

dsyme avatar Apr 17 '24 22:04 dsyme

I did look at 679 ad the use case mentioned by @Lanayx and didn't see that Pulsar.Client had actually needed to define IWSAMs in C# as a result of the lack of authoring in F#.

Right, I didn't need IWSAMs, only default interface methods. To overcome lack of those I had to use abstract classes and inheritance instead of interfaces.

Lanayx avatar Apr 18 '24 00:04 Lanayx

F# draws a line in the sand and says "no" to "protected" to say "this is not the way you share things in F#, this is not the way you abstract, this is not the way you encapsulate, this is not the way you design".

Interestingly, @dsyme, last year you actually did approve protected to be added to the language: https://github.com/fsharp/fslang-suggestions/issues/157#issuecomment-1505246960.

(this is not to suggest I am in favor or against, in fact, I don't really have an opinion on protected, and I've yet to form an opinion on the feature addressed in this PR ;))

abelbraaksma avatar May 08 '24 01:05 abelbraaksma