7 ms·
C# almost has implicit interfaces
- tgma 2y agoThat's a single function pointer not an interface which is a named set of function pointers.
- SideburnsOfDoom 2y agoAn interface is a named set of one or more function pointers. (actually zero or more if you use empty interfaces too). But yes, this idea is for the case where there's exactly one interface member, then it's equivalent to a function pointer.
- tgma 2y agoDid you just misquote me and "well, actually'd" your own paraphrase? I never used "one or more" :)
- SideburnsOfDoom 2y agoWhat is this catchphrase word salad trying to convey? I can see exactly what you used and didn't use, in your one sentence long comment above. Why assume that you're being quoted at all? Empty interfaces or "marker interfaces" are allowed by the c# language: public interface IDoNothing { }; But no style guide will recommend them, and they are seldom seen in code, as there are better ways to accomplish the same thing. So they're technically there, but are usually overlooked, that's all. Single-method interfaces are common, and are roughly equivalent to a function pointer. Multi-method interfaces are also common, and are not. It's not about you.
- JTyQZSnP3cQGa8B 2y agoI always wondered how that would work in a medium or large codebase. Is it easy to refactor? Refactoring can change the interface of classes, which is easier if they are explicit. And my main fear: how do I know I use the right object? I would tempted to add methods all the time to satisfy the target interface instead of using another object that already satisfies that interface. Not knowing explicitly whether I can use an object or not is confusing, or am I missing something obvious? Edit: Last but not least, how do I know my classes implement an interface that could require 5 or 10 methods? I had to do that in Go, and counting and searching the methods was really a waste of time, so much that I had to add comments to explain the interfaces that were implemented in every class.
- riwsky 2y agoYes. In particular, it means you can switch from depending on a concrete type (say, a whole S3Client) vs depending on just a much smaller interface (eg if you only want GetObject) without having to touch anyone else’s code. The compiler will still check that the types being assigned to the interface meet those constraints.
- vips7L 2y agoSounds a lot like just using functions. I do that a lot if there is only a single function from a type I need. I’m yet to decide if that’s a mistake or not.
- o11c 2y agoAsk any C++ programmer, this is a nightmare.
- JTyQZSnP3cQGa8B 2y agoI’m a C++ programmer, and the few times I had to deal with this kind of thing in Go or Ruby, it was painful. Remove a method? Your code does not compile anymore and you have to refactor in places where the type of the class was not obvious or explicit at all.
- deschutes 2y agoI think the dark side of it is unintentional ADL but it's mostly good. C++ gets a lot of mileage out of concepts in templates. The newer language feature just makes the existing practice first class as something a bit beyond syntactic sugar. But the design of the STL relies heavily on the idea that if something has the requisite properties it is that thing. Iterators are probably the most prominent example. You won't find any explicit iterator types. It's just a set of properties that a type has that makes it an iterator or not. Personally I've never seen confusion be an issue. Granted the iterator and algorithm design has been mostly superceded by newer apis that operate as pipes of functions over streams. Frankly the experience of these are poor in C++ for various reasons. One big example is that keys in associative collections are const qualified even when moving from the collection. The constness doesn't match the expectation users have when consuming a collection and is unfixable within the constraints of the STL. Anyway it results in awful type checking errors. The whole library is full of these foot guns and most of them result in bizarre behavior or horrible error messages. Iterators have their own warts but IMO work much better within the C++ type system. Here's a fun one. Reverse iterators have their own set of invalidation properties which are typically weaker and different than forward iterators. Due to various reasons they actually refer to the element that precedes (in reverse sequence) the one you'll get when dereferencing it. So end is rfront and front is rend. In either case the experience is quite bad compared to the stream apis you get from rust. But I don't think this is a mark against concepts, just a dated design and the limits of the type system and semantics of the language.
- kevingadd 2y agoIn general, problems that some languages would solve with an interface containing 1-2 methods can be solved simply via delegates in C#. Then you can bind to any method with a compatible signature, or to an anonymous closure, or to a local function, without any fuss. Compared to the bad old days of having to write a whole anonymous class in java, it's pretty straightforward. Thankfully things like ValueTuple are built-in now alongside ref/out parameters, so producing multiple return values is no problem. The downside is usually a method with a compatible signature doesn't have compatible behavior. This is part of why explicit interfaces are still valuable.
- issafram 2y agoEh I don't see the need for it. Explicit is the way to do. The biggest mistake they've made in recent memory was adding default implementations of interfaces. Makes no sense.
- ed_elliott_asc 2y agoIt would be useful in testing, mocking (faking) sealed classes etc
- masklinn 2y agoYou can do that with explicit implementations just fine e.g. haskell type classes (and their less competent sibling rust traits). It is, if anything, better: you can abstract over multiple third party types, and you’re not stuck with the interface they defined, so if you need to switch in the future you can.
- banashark 2y agoF# has an interesting way to achieve a similar function in Statically Resolved Type Parameters (SRTP) Palatable blog post: https://www.compositional-it.com/news-blog/static-duck-typing-in-f/ https://www.compositional-it.com/news-blog/static-duck-typin... Official docs: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/statically-resolved-type-parameters https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...
- fire_lake 2y agoI sort of wish the defaults were flipped in F# - everything is inline by default.
- Smaug123 2y agoCould you clarify that? Are you saying you wish everything could be inline by default? (It's not true that things are currently inline by default.)
- fire_lake 2y agoYes I would prefer compile-time duck-typing everywhere by default. Well, I think that I would! It probably has unintended consequences.
- zogrodea 2y agoThat's an interesting feature I didn't know F# had. It sounds similar to row polymorphism (which I found a good description of here: https://news.ycombinator.com/item?id=7829766 https://news.ycombinator.com/item?id=7829766 ).
- g15jv2dp 2y agoSo if I declare a couple of methods named "Foo" and "Bar", and someone somewhere declares an interface FooBar consisting of those two method names - and bad luck, same signature - then I have to make sure I follow the semantics of that interface I might not even know about, because even if I don't declare it so, anyone could use my class and push it through the FooBar-shaped hole. That's absurd. Exercise: imagine what the semantics of the following signature are: `int Read(string)`. Did everyone get the same answer? And yet, with implicit interfaces, you absolutely need everyone to settle on the same answer. Otherwise, person A could write a class with such a method with answer A in mind, person B could write a library declaring an interface with such a method with answer B in mind, and person C could use the class from person's A code and the interface from person B's code without realizing.
- mervz 2y agoThis is how Go works and there's really no issue... not sure what the gripe is here.
- 38 2y agoYeah I was about to say, it's literally structural typing https://wikipedia.org/wiki/Structural_type_system https://wikipedia.org/wiki/Structural_type_system
- lmm 2y agoThere are plenty of issues. You simply can't write a method that e.g. accepts a read-only collection but does not accept a mutable collection. You can't use a "marker" interface with no methods/members to indicate intent. I mean sure you can write programs in it and they'll work most of the time, but if that's what you want you might as well use an untyped language.
- jayd16 2y agoCan you do that in C# now? Not with IReadOnlyCollection and the mutable collections implement IReadOnlyCollection.
- chris_wot 2y agoIsn't this just a peculiar brand of parameterized type?
- WhereIsTheTruth 2y agoYou can in D struct Foo { void read(){} } struct Bar { void read(){} } struct Nope { } void handle(T)(T data) { static if (__traits(hasMember, data, "read") == false) static assert(0, "struct needs a 'read' function"); data.read(); } void main() { Foo foo; Bar bar; Nope nope; handle(foo); // works handle(bar); // works handle(nope); // oops main.d(17): Error: static assert: "struct needs a 'read' function" }
- beart 2y agoI'm a bit surprised by the comments here. Isn't this just duck typing, which has been a thing in dynamic languages for a very long time? The supposed risks of breaking the contract by changing the class ring hollow to me. The implicit interface you have created is already public. So how is that any different than changing any other public API? There is no suggestion of an implicit interface over private methods. I've worked in c# and typescript. I don't think this feature is particularly needed in c#, but I also don't see the issues presented by other comments as real problems.
- 8n4vidtmkvmk 2y agoWhat about methods that share a signature but have a different meaning? The interface can have a comment documenting what it's supposed to do. Any class/function that explicitly implements said interface should adhere to that definition/meaning. Any function that implicitly implements it... who knows what was intended.
- beart 2y agoIn theory, you have a point. However, in practice it doesn't matter. It is extremely unlikely that the code is written in a way where random, unknown implementations are being passed around and called.
- UglyToad 2y agoI've been experimenting with this, it makes testing trivial and removes the coupling that inevitably occurs with multi method interfaces. However I think there's one missing enhancement that would turn it from esoteric and difficult to reason about to actually usable that the language will never get. This is being able to indicate a method implements a delegate so that compilation errors and finding references work much more easily. E.g. suppose you have: delegate Task<string> GetEntityName(int id) public async Task<string> MyEntityNameImpl(int id) I'd love to be able to mark the method: public async Task<string> MyEntityNameImpl(int id) : GetEntityName This could just be removed on compile but it would make the tooling experience much better in my view when you control the delegate implementations and definitions.
- jayd16 2y agoIf you want to enforce things, use an interface. If you want to accept anything that fits use a delegate. I'm not sure I understand your use case where you need to conflate the two. You want to enforce the contract but with arbitrary method names? I suppose you could wire up something like this but it's a bit convoluted. interface IFoo { string F(String s); } class Bar { public string B(String s){ return ""; } } // internal class, perhaps in your test framework class BarContract : Bar, IFoo { public string F(string s) => B(s); }
- neonsunset 2y agoIf anything, there is little reason to use a named delegate over the Func nowadays too. The contract in this case is implied by you explicitly calling a constructor or a factory method so a type confusion, that Go has, cannot happen.
- UglyToad 2y agoThe idea with the named delegate would be if you need some way to: delegate Task<string> GetUserEmail(int userId); This provides more guidance than taking in a: Func<int, Task<string>> getUserEmail If you can annotate implementations of the delegate the tooling support becomes even nicer. Not all Funcs with the same shape have the same semantics, in my ideal C#-like language. Edit: I completely forgot the main reason which is if using a DI container it can inject the named delegate for you correctly in the constructor. Versus only being able to register a single func shape per container.
- SideburnsOfDoom 2y agoIn other words: an interface with one member is equivalent to a function reference. There is an equivalence between public interface IThingDoer { int DoTheThing(int value); } and Func<int, int> doTheThing. Converting from one to the other is left as an exercise to the reader.
- agumonkey 2y agoSo it's a kind of interface narrowing ?
- swisniewski 2y agoExtension methods complicate implicit interfaces considerably. This is likely one of the reason they don’t exist in C#. It’s also one of the reasons GO doesn’t have extension methods. https://github.com/golang/go/issues/37742#issuecomment-596167605 https://github.com/golang/go/issues/37742#issuecomment-59616... You either have to exclude extension methods from implicit interface definitions (which can feel very unnatural to consumers) or you get weird behavior with dynamic casts that is very confusing and breaks everything. For that reason, its unlikely you would see this in C#. VB tried to do this (implement both extension methods and dynamic interfaces), and ended up cutting dynamic interfaces because the two features don’t play well together. They are an awesome feature in GO, but it’s hard to add them to C# without a whole lot of very messy design compromises that make it kind of wonky. If you eliminate extension methods it’s much easier to add dynamic interfaces.
- cryptonector 2y ago> I though it was clever, and I was a bit enamored when I first heard it (having come from a C# background). Now, I think implicit interfaces are simply, obviously right. What, no. No, implicit interfaces are not right. They are a footgun. That you provide some interface needs to be declared. I dislike unnecessary ceremony as much as anyone, but this is necessary ceremony.