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

Allow implementing the same interface at different generic instantiations in the same type [ RFC FS-1031 ] #545

Closed
1 task
0x53A opened this issue Feb 24, 2017 · 10 comments

Comments

@0x53A
Copy link
Contributor

0x53A commented Feb 24, 2017

Note: this is a duplicate, but the original proposal was reduced down to only enable inheritance of such types, not implementation in F#.
I would like to ask you to reconsider fully implementing this feature.

Allow implementing the same interface at different generic instantiations in the same type

I propose we ...

Allow to implement the same interface multiple times with different generic instantiations.

E.g.

type I<'T> =
    abstract member M : unit -> 'T

type T() =
    interface I<int> with
        member x.M = 1
    interface I<bool> with
        member x.M = true

The existing way of approaching this problem in F# is ...

The existing workaround is to create one extra level of inheritance per instantiation.

Pros and Cons

The advantages of making this adjustment to F# are ...

Feature parity with C#.

Reduce surprise for new developers.

Remove boilerplate inheritance chains caused by the workaround.

The disadvantages of making this adjustment to F# are ...

Quoting from UV:

[...] This is partly because of the way interface implementation methods are named in compiled IL (only the prefix "I" is added), and partly because the equivalent object expression form can inculde type unknowns, e.g.

{ interface I<> with ...
interface I<
> with ...

and we don't want to support this kind of inference, and equally don't want a non-orthogonality between object expressions and class definitions. [...]

Extra information

Estimated cost (XS, S, M, L, XL, XXL): As this contains changes to code generation and type inference, this is probably M-L, but I am not familiar enough with the code base.

Related suggestions:

Original SO: http://stackoverflow.com/questions/1464109/implementing-the-same-interface-at-different-generic-instantiations
Original UV: https://fslang.uservoice.com/forums/245727-f-language/suggestions/5663504
Original PR: dotnet/fsharp#18

Affadavit (must be submitted)

Please tick this by placing a cross in the box:

  • [ x ] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • [ ] I have searched both open and closed suggestions on this site and believe this is not a duplicate (It is a duplicate of the original suggestion)
  • [ x ] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it. (As I understand it, the previous decision was more for technical reasons, e.g. the naming of interface methods)

Please tick all that apply:

  • [ x ] This is not a breaking change to the F# language design
  • [ x ] I would be willing to help implement and/or test this (Willing yes, capable of implementation probably not. I would love to give it a try If you would be willing to hold my hand)
  • I or my company would be willing to help crowdfund F# Software Foundation members to work on this
@dsyme
Copy link
Collaborator

dsyme commented Mar 1, 2017

@0x53A I agree this should be done, as long as the disadvantages tracked above are taken into account int the implementation

@0x53A
Copy link
Contributor Author

0x53A commented Apr 16, 2017

@dsyme when looking at how C# implements this, I noticed that apparently, Methods don't need to be named uniquely in IL:

image

image

image

ClassLibrary that is imported with alias=al

namespace Namespace
{
    public class Class
    {
    }
}

Console App:

extern alias al;

namespace Namespace
{
    public class Class
    {
    }
}


namespace ConsoleApp1
{
    public interface IA<T>
    {
        object Get();
    }

    public class MyClass : IA<int>, IA<string>, IA<al.Namespace.Class>, IA<global::Namespace.Class>
    {
        object IA<int>.Get()
        {
            return 1;
        }

        object IA<string>.Get()
        {
            return 2;
        }

        object IA<global::Namespace.Class>.Get()
        {
            return 3;
        }

        object IA<al.Namespace.Class>.Get()
        {
            return 4;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var x = new MyClass();
            System.Console.WriteLine((x as IA<al.Namespace.Class>).Get());
            System.Console.WriteLine((x as IA<global::Namespace.Class>).Get());
            System.Console.ReadLine();
        }
    }
}

So unless fsc itself has issues with duplicated names, it may be as easy as just removing the check.

I will try to create a prototype over the next few days / weeks / months / years =)

@0x53A
Copy link
Contributor Author

0x53A commented Apr 16, 2017

RFC: under discussion
prototype: In progress

@dsyme dsyme added needs rfc and removed needs rfc labels Dec 1, 2017
@dsyme dsyme changed the title Allow implementing the same interface at different generic instantiations in the same type Allow implementing the same interface at different generic instantiations in the same type [ RFC FS-1031 ] Dec 1, 2017
@MarkNicholls
Copy link

MarkNicholls commented Oct 22, 2020

I have a wish to do this (which isnt allowed in C# due to the possibility of unification).

type IOr<'a,'b> = 
    abstract member Else<'c> : ('a -> 'c) -> ('b -> 'c) -> 'c

type IOr<'a,'b,'c> = 
    inherit IOr<'a,IOr<'b,'c>>
    inherit IOr<'b,IOr<'a,'c>>
    inherit IOr<'c,IOr<'a,'b>>

Is this going to be valid?

naively I would hope the unification issue would either be handled when constructing the type...i.e.

IOr<int,int,string>

isnt valid but

IOr<int,char,string>

is

@cartermp
Copy link
Member

@MarkNicholls No, that is not possible in F# for the same reason.

@MarkNicholls
Copy link

MarkNicholls commented Oct 27, 2020

@cartermp

sorry, let me make it a bit more concrete.

C# allows this

interface IFoo : IEnumerable<int>, IEnumerable<string> { }
and F# doesnt....which shouldnt in theory be an issue (I'd hope).

C# doesnt allow this

interface IFoo2<A, B> : IEnumerable<A>, IEnumerable<B> { }

it complains

Error CS0695 'IFoo2<A, B>' cannot implement both 'IEnumerable<A>' and 'IEnumerable<B>' because they may unify for some type parameter substitutions
I'm asking whether the scope of this suggestion covers the latter.
My preference is that it does, but that it rejects

interface IBar : IFoo2<int,int> {}
i.e. it kicks the can down the road to the point where the type is constructed, and the compiler discovers that the types do unify (personally I'd be tempted to have this as a warning and always dispatch on the 1st interface...but I suspect that will make people nervous).

@cartermp
Copy link
Member

cartermp commented Oct 27, 2020

IEnumerable is a tricky one since the rules for interface implementations are a bit difference, and you'd need to do this:

open System.Collections
open System.Collections.Generic

type C() =
    interface IEnumerable<int> with
        member this.GetEnumerator(): IEnumerator<int> = 
            raise (System.NotImplementedException())

    interface IEnumerable<string> with
        member this.GetEnumerator(): IEnumerator<string> = 
            raise (System.NotImplementedException())

    interface IEnumerable with
        member this.GetEnumerator() : IEnumerator =
            failwith "yeet"

So that shouldn't be an issue.

The example you provided previously will not work for the same reasons it doesn't work in C#. The error is:

image

@MarkNicholls
Copy link

Ok,

IEnumerable<> was just a convenient example of inheriting an interface...I don't actually want to do it.

So the bottom line is the scope of this doesnt extend to my latter example, hmmmm....should I open a suggestion to include that? or is that premature?

@cartermp
Copy link
Member

I suppose it's worth adding a new suggestion.

@cartermp
Copy link
Member

cartermp commented Nov 8, 2020

Closing out as completed for F# 5.

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

4 participants