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

Support implicit interface implementation #195

Closed
baronfel opened this issue Oct 20, 2016 · 11 comments
Closed

Support implicit interface implementation #195

baronfel opened this issue Oct 20, 2016 · 11 comments

Comments

@baronfel
Copy link
Contributor

baronfel commented Oct 20, 2016

Submitted by Mastr Mastic on 3/23/2014 12:00:00 AM
133 votes on UserVoice prior to migration

F# only supports explicit interface implementation with the price of unnecessary and excessive casting (also causes readability issues), potential confusion when working with other languages, and causes limitation with F#'s OOP that could be resolved without any major language change.

To expand on this, this post by Mauricio Scheffer describes the issue very well. In addition, I'm sure I could argue that people would prefer to just write identifier.Member rather than (identifier :> Type).Member whenever the member is a signature provided by an interface. This repeats quite a bit on average and it is messy, to say the least.

It should also be pointed out that even though F# is primarily functional (and interfaces are less of an issue), F# code is still being used from other languages, and also some domains and tasks could be easier to take on from an OO approach.(I am also finding very high limitations with using WPF & XAML with some MVVM approaches)

Currently the workaround would be to copy-paste the signatures for every member of an interface.

Original UserVoice Submission
Archived Uservoice Comments

@dsyme dsyme added area: objects and removed open labels Oct 29, 2016
@dsyme dsyme changed the title Allow Implicit Interface Implementation Allow implicit interface Implementation Oct 29, 2016
@dsyme dsyme changed the title Allow implicit interface Implementation Support implicit interface implementation Oct 29, 2016
@Rickasaurus
Copy link

Wouldn't this lead to additional type checking ambiguity and so potentially break existing code?

@lfr
Copy link

lfr commented Jun 5, 2019

I agree that while the current approach is perfectly fine in purely (or mostly) functional scenarios, when moving parts of large enterprise solutions to F# there may be a lot of Interop involved and the current syntax seems unnecessarily verbose.

As far as I can see I don't think this adds any ambiguity, while the new hypothetical code omits the explicit implementation, this implementation would still be there (implicitly), any checks would basically be performed against the same objects/functionality. Existing code can only call explicit implementations (it's the only thing available right now) and such implementations are by definition always there for existing code since they have to be explicitly defined in the code atm, I don't see any room for ambiguity but I'd like to hear other people's opinions.

@smoothdeveloper
Copy link
Contributor

I'd like to have more examples, and wonder if other suggestions wouldn't intersect in some ways that would alleviate the kind of extra hoops to merge several interface calls at client site.

Interface is going to get "overloaded" in future versions of the framework, and I like to have F# leaning toward privately implemented interfaces.

#501 + something like

type MyComp() =
  interface IComparer as icomp with
     // ...

 interface IFoo as ifoo with
    // ...

 // publish members
 members from icomp
 members from ifoo

so long there is no conflict, it would just compile by exposing the members publicly.

@lfr
Copy link

lfr commented Jun 5, 2019

@smoothdeveloper if I understand your suggestion correctly, this is an elegant approach, everything is explicit yet with minimal verbosity

Edit: I do see a potential inconvenience though, if IFoo requires and implementation that's already provided when implementing IComparer, does it have to be also provided for IFoo?

@smoothdeveloper
Copy link
Contributor

@lfr,

#501 alleviates the pain of authoring multiple interfaces in a type while also using those locally in the implementation of the type.

To also help on the client / caller side by exposing all members easily, it would require to add a syntax such as members from (expression), it could be seen as kind of an open statement to dispatch public members from a local.

if IFoo requires and implementation that's already provided when implementing IComparer, does it have to be also provided for IFoo?

expected usage

open System.Collections
type IFoo =
    abstract member FooCompare : a: IFoo -> b: IFoo -> int
    abstract member RegularCompare : a: obj -> c: obj -> int

let mkFoo = fun _ -> Unchecked.defaultof<_>
let foo = MyFoo(mkFoo)
foo.Compare(1, 1)
foo.FooCompare(mkFoo 1, mkFoo 1)

light interface authoring mode:

type MyFoo(foo: obj -> IFoo) =
  interface IComparer as icomp with
    member x.Compare(a, b) = Unchecked.defaultof<_>
  interface IFoo as ifoo with
    member x.FooCompare a b = a.FooCompare(foo a, foo b)
    member x.RegularCompare a b = icomp.Compare(a,b)
  members from icomp
  members from ifoo

currently

type MyFoo(foo : obj -> IFoo) as this =
  let ifoo = this :> IFoo
  let icomp = this :> IComparer
  interface IComparer with
    member x.Compare(a, b) = Unchecked.defaultof<_>
  interface IFoo with
    member x.FooCompare a b = a.FooCompare (foo (box a)) (foo (box b))
    member x.RegularCompare a b = icomp.Compare(a,b)
  member x.Compare(a,b) = icomp.Compare(a,b)
  member x.FooCompare a b = ifoo.FooCompare a b
  member x.RegularCompare a b = ifoo.RegularCompare a b

Would that make sense?

@lfr
Copy link

lfr commented Jun 5, 2019

It's probably a given but just to make sure, would the following work with aliases if RegularCompare had exactly the same signature as IComparer.Compare?

type MyFoo(foo: obj -> IFoo) =
  interface IComparer as icomp with
    member x.Compare(a, b) = Unchecked.defaultof<_>
  interface IFoo as ifoo with
    member x.FooCompare a b = a.FooCompare(foo a, foo b)
    member x.RegularCompare = icomp.Compare // ← this
  members from icomp
  members from ifoo

@smoothdeveloper
Copy link
Contributor

@lfr, given this code type checks right now, I'd say yes:

open System.Collections
type IFoo =
    abstract member FooCompare : a: IFoo -> b: IFoo -> int
    abstract member RegularCompare : a: obj * c: obj -> int

type MyFoo(foo : obj -> IFoo) as this =
  let ifoo = this :> IFoo
  let icomp = this :> IComparer
  interface IComparer with
    member x.Compare(a, b) = Unchecked.defaultof<_>
  interface IFoo with
    member x.FooCompare a b = a.FooCompare (foo (box a)) (foo (box b))
    member x.RegularCompare(a,b) = icomp.Compare(a,b)
  member x.Compare(a,b) = icomp.Compare(a,b)
  member x.FooCompare = ifoo.FooCompare // can be done here
  member x.RegularCompare = ifoo.RegularCompare // also here


let mkFoo = fun _ -> Unchecked.defaultof<_>
let foo = MyFoo(mkFoo)
foo.Compare(1, 1)
foo.FooCompare (mkFoo 1) (mkFoo 1)
foo.RegularCompare (mkFoo 1, 1)

@lfr
Copy link

lfr commented Jun 5, 2019

In this case, I am of the opinion that the combination of aliases together with this issue's suggestion is a solid improvement for Interop scenarios. Perhaps we if we consider all of these interface-related suggestions as one package the benefit to effort ratio makes it more appealing when prioritizing.

@matthid
Copy link

matthid commented Jun 5, 2019

I'd suggest going the other way around as #132 suggests.

IMHO we should extend #132 to include this use-case - and completely replace this suggestion - considering that today I do not write code with casts as @smoothdeveloper has suggested. Instead I write:

type MyFoo(foo : obj -> IFoo) as this =
  member x.Compare(a,b) = Unchecked.defaultof<_>
  member x.FooCompare a b = a.FooCompare (foo (box a)) (foo (box b))
  member x.RegularCompare a b = x.Compare(a,b)
  interface IComparer with
    member x.Compare(a, b) = x.Compare(a,b)
  interface IFoo with
    member x.FooCompare a b = x.FooCompare a b
    member x.RegularCompare a b = x.RegularCompare a b

Which is already shorter and IMHO easier to follow. With extending #132 this could be simplified to

type MyFoo(foo : obj -> IFoo) as this =
  member x.Compare(a,b) = Unchecked.defaultof<_>
  member x.FooCompare a b = a.FooCompare (foo (box a)) (foo (box b))
  member x.RegularCompare a b = x.Compare(a,b)
  interface IComparer by this
  interface IFoo by this

Which again is Imho easier to follow and more useful in general.

The disadvantage is that you might need to add type-annotations (in this case a.FooCompare wouldn't work and you would need an annotation). On the other hand with the alternative you need casts (which are basically type-hints as well).

The "only" thing we need to change in #132 is that it need to work on object structure and not on types (ie the compiler should check that the object after by contains all methods - NOT that it is of the type of the interface)

@arialdomartini
Copy link

Honestly, I don't see the reason to have implicit implementation. I can't think of a case in which an instance passed in terms of an interface should be cast down to the concrete type.

Isn't the whole idea behind Programming to the Interface to base programming logic on the objects interfaces, rather than on internal implementation details? It seems to me that F# promotes the compliance with the Dependency Inversion Principle: if identifier is injected / passed as an interface, not as its concrete type, there there should be no reason to invoke its members in terms of the implementation, with (identifier :> Type).Member, and F# enforces this preventing the concrete type from exposing members defined in the interface.

If one really needs that, chances are the code can be refactored to be independent from the identifier implementation, or maybe identifier needn't an interface from the beginning. If there's an interface, why ever circumventing it?

@dsyme
Copy link
Collaborator

dsyme commented Apr 12, 2023

I'm going through very old design issues either marking them as approved or closing them.

I am marking this one as declined, as ultimately I don't think this fits with the F# approach to object programming.

@dsyme dsyme closed this as completed Apr 12, 2023
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

7 participants