C# almost has implicit interfaces

53 points by mwsherman 11 months ago | 71 comments
  • g15jv2dp 11 months ago
    So 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.

    • jayd16 11 months ago
      But parameters don't just fall into FooBar shaped holes. Someone passes them in there. Its still an explicit choice by the programmer to not read the docs and just roll the dice. Who thinks "I fits, I sits" is sufficient for a proper program anyway? Is it really a downside to allow this kind of munging?

      Personally I want to see Extension Interfaces, so you opt into such a system in a slightly more explicit way. The slightly extra work aides in tooling and documentation but I can see how Go's way is not absurd.

      • scratcheee 11 months ago
        That argument works pretty well for any bad api design (assuming sufficient documentation somewhere).

        Yet I assume we can agree that regardless of how you can work around bad apis, good api design that prevents misuse is always better.

        The safety measures have to stop somewhere of course (short of an api that is a single function which does exactly the thing you want without inputs or outputs, which seems unlikely), but extending type safety to interfaces does not seem like a step too far.

        • jayd16 11 months ago
          Another aspect of the issue is to consider the asymmetric nature of knowledge of the API author and the API consumer.

          When one authors a type in a language like C# they must predict how that type will be used and what interfaces the author promises the type can be.

          The API consumer might be unfamiliar with the types in an API at first, but ultimately they will know more about how they want to use the type and what additional contracts they think the type fulfills.

          As it is, this knowledge is only useful when inheriting a type. There is no facility to "vouch" for a type in C#, currently. In Go structural typing fulfills this.

          I think Extension Interfaces is the best of both worlds.

      • shermantanktop 11 months ago
        Exactly. Contracts are over shapes, types, behaviors, error-handling, the whole kit and kaboodle. Not just matching a call signature.
        • tdb7893 11 months ago
          People could be shoving square pegs into coincidentally square holes but having coded in Go for years I haven't seen this scenario in practice ever. It would take a lack of understanding of the objects you're working with combined with a pretty big gap in your tests to cause a problem and at that point it's still not the most likely bug you'll see
          • onionisafruit 11 months ago
            I agree with you, but I can think of one counter example from Go. “crypto/rand.Rand” and “math/rand.Rand” both satisfy “io.Reader” which has lead to people inadvertently using the less secure choice.

            That said, I think it is less about implicit interfaces and more about confusion between similar namespaces. After all “bytes.Buffer” also satisfies “io.Reader”, and I don’t see people confusing it with “crypto/rand.Rand”.

            • jayd16 11 months ago
              How does go handle marking up types with interfaces and then using types without that explicit interface?

              Do devs use the explicit interfaces at all? Do they treat implicitly casted types with more scrutiny? Does the tooling care?

            • mervz 11 months ago
              This is how Go works and there's really no issue... not sure what the gripe is here.
              • 38 11 months ago
                Yeah I was about to say, it's literally structural typing

                https://wikipedia.org/wiki/Structural_type_system

                • lmm 11 months ago
                  There 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 11 months ago
                    Can you do that in C# now? Not with IReadOnlyCollection and the mutable collections implement IReadOnlyCollection.
                • hansvm 11 months ago
                  > That's absurd.

                  The whole point of an interface is to allow for multiple concrete implementations. What hidden requirements are you suggesting which would make the act of opting in to known working interfaces a good idea?

                  Separately, there's a bit of tension generally between authors wanting to limit promises made to callers and callers wanting to use any code that's good enough for their end application. Compile-time duck typing (implicit interfaces or structural typing or whatever) is a decent tradeoff for that. Something like a `fn ReadTwice(fn Read(string) int) fn (string) int` combinator almost doesn't care about your particular semantics, but that sort of generic code is impossible to write in a world with sealed classes, opt-in interfaces, and other sorts of features taking power away from callers (who have the appropriate context to know whether it's reasonable to use your code) and giving it to library authors (who want to support a narrow enough use case to guarantee their code is correct and tell everyone else to fuck off). Even just having two separate IReader interfaces or ISleeperClock or IClock or ITime or all the other sorts of permutations you might find in an ecosystem can cause major friction without actually adding any type safety.

                  • gwervc 11 months ago
                    > The whole point of an interface is to allow for multiple concrete implementations.

                    The issue is that in this case the second method may not be an implementation of the interface at all in the first place, simply a method that happens to have the same signature. That can happen easily when parameters are only built-in or BCL types.

                    • hansvm 11 months ago
                      Why does that matter though? Looking at the concrete `Read` example above, what semantics might apply to an IReader vs a class which happens to accidentally conform to the interface? If a person asked for an IReader argument and found your class instead, why would they be upset?
                      • jen20 11 months ago
                        The problem here is one of perspective. Interfaces in Go belong to consumers not producers.

                        So sure, your “I used something that matches X but isn’t really X” is possible, but practically it doesn’t happen.

                    • nly 11 months ago
                      C++ concepts have the same problem. In practice is a non-issue for the language because templates already had this issue.

                      In old-school C++ you would probably handle this with traits. I.e. a specialization of a tag type that can be written by either the class author or a third party that indicates a types semantic compatibility with a concept.

                      Example: specialising std::hash instead of forcing everyone to add hashCode() members

                      • gpderetta 11 months ago
                        And you can implement pseudo-nominal conformance to a concept Foo by requiring the presence of a 'yes_this_class_really_conforms_to_Foo' tag.
                      • masklinn 11 months ago
                        In my experience this really is not an issue. There’s an intentionality to using objects and you’re not a maddened fuzzer trying to plug random objects into arbitrary functions with no rhyme or reason.

                        It’s essentially a less likely version of using the wrong callback, something which has undoubtedly happened in the fullness of time but is of no real concern.

                        No in my opinion the issue is the opposite: implicit structural interfaces make it harder to discover what interfaces a type implements, and what you can do with it.

                        A secondary effect being that mismatches have worse reporting, whether you’re trying to implement an interface or the interface has changed from under you the compiler only reports use site so from there you have to did out what the type is and why it does not conform anymore, things get worse if side casts are involved. There’s actually a pattern for checking conformance:

                            var _ Iface = (*Type)(nil)
                        
                        Mmm yummy.

                        Oh yeah and if the interface removed a method and you didn’t realise you might be dragging that useless methods for a long while. Then again it’s not like your Java-style interface is any different.

                        • tryfinally 11 months ago
                          > Oh yeah and if the interface removed a method and you didn’t realise you might be dragging that useless methods for a long while. Then again it’s not like your Java-style interface is any different.

                          In C# I usually use explicit interface implementations. (They're inconvenient to type, but Rider has a macro for it.) When the interface changes or disappears, my code won't compile.

                          • masklinn 11 months ago
                            Didn’t know that existed. It’s a bit of a half assed version of half of type classes but it’s an improvement that this is at least available.

                            Can you also implement interfaces on types you didn’t define?

                        • juki 11 months ago
                          I think a lot of people here are being confused by all the talk about "implicit interfaces" in the article. It's not actually talking about interfaces in C# at all, just about the old-fashioned way of passing callbacks.

                          If you consider that a problem, you would also have the same problem in any other language with first class functions. Someone might define a `readSomething(string -> int) -> ...` function. Does that mean everyone who now defines a `string -> int` function must make it suitable for `readSomething`? Obviously not. It's up to the caller to pass correct arguments to the functions they are calling.

                          • za3faran 11 months ago
                            There was a bug in the golang standard lib that was basically exactly due to that behavior. It seems that even the golang authors made such a mistake.
                            • masklinn 11 months ago
                              I can’t find it but IIRC the issue was mostly the internal use of reflection, possibly undocumented e.g. a function. Would take a reader, then do a thing, then close it if possible.

                              But it did not take a closeable reader, it’d just ask for a reader and close it from under you. And maybe this caught people who did not intend to implement a readcloser but it definitely caught people who just didn’t want their file closed because they had shit to do with it afterwards.

                            • trustno2 11 months ago
                              Your hypothetical issue is that you accidentally make a function conforming to an interface without actually meaning the same thing that the interface means?

                              That almost never happens in reality so it's not an issue.

                              For example, go has io.Reader which needs just `Read(p []byte) (n int, err error)`

                              Your issue is that you accidentally do Read with same signature, but it will mean something else, and the error it returns are different, so instead of EOF (as io reader should) it will return something else on end of file?

                              ... it just doesn't happen in Go. You would need to go out of your way to break it. I guess it theoretically can happen.

                            • JTyQZSnP3cQGa8B 11 months ago
                              I 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.

                              • lolinder 11 months ago
                                I haven't done much Go, but TypeScript has similar characteristics with its interfaces and I haven't found refactoring to be much of an issue. If the interface changes then the type checker will alert you to all the places where you now have the wrong method type. The main difference between this and a Java-style explicit interface is that you have to make one jump from the error at the point of use to the definition of the class or record, where in Java the error points you right to the class.

                                > 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.

                                Why don't you do this with explicit interfaces? It's one extra line of code beyond the method implementation, and it's a backwards-compatible change. I suspect that the main reason you don't is because you know that class Foo shouldn't be a Bar, and the same logic can easily guide you with implicit interfaces.

                                > Not knowing explicitly whether I can use an object or not is confusing, or am I missing something obvious?

                                This one I'll agree with. Implicit interfaces work well in quick code that you're writing yourself that fits in your head. When you're trying to understand a library someone else wrote, though, being able to have the IDE list all the implementations of an interface is very valuable.

                                Also, explicit interface tagging communicates authorial intent in a way that isn't possible with implicit interfaces.

                                • riwsky 11 months ago
                                  Yes. 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 11 months ago
                                    Sounds 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 11 months ago
                                    Ask any C++ programmer, this is a nightmare.
                                    • JTyQZSnP3cQGa8B 11 months ago
                                      I’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 11 months ago
                                        I 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.

                                      • Quothling 11 months ago
                                        We've switched a lot of our C# to Go because it works better for a lot of our concurrency computation, and I've found that it's easier to refactor because you have a single implementation of an "interface". I say that in quotes because Go's interface{} isn't really like a C# interface and what you'd use for "classes" in Go is typically structs.

                                        I think it's easier to think of Go as a mix of Python, Typescript, C++ and others, making sort of the same re-implementation that Java/C# originally did with a more modern approach. Please not that this is neither completely correct and opinionated, but I think it's a good way to explain it. Similarly I think Typescript is a better way to think of objects in Go than what you may be used to coming from C#. Stucts work much like Type/Interface in Typescript and you're not going to have issues with it because anything you change will be immediately obvious in your code. It also means your functions live in isolation from the objects, and this is perhaps one of the "weaker" parts of Go coming from C# because it won't be blatantly obvious when you're working with an object until you get the working behind = assignment and := declaration + assignment. On top of that you have the Go interface{} which isn't really comparable to a C# interface and it's much easier to think of it as the Typescript "Any/Unknown" type. This isn't exactly true because it's an unknown type where all you basically know is that it holds no methods, meaning that unlike the Any type in Typescript and somewhat similar to the Unknown type {} is actually useable in Go.

                                        I don't think there is a good reason to chose C# for new projects if Go is an option for you. I don't think there is any reason to use Go if you plan on using C#, maybe because that is what your team does well. We did it because we needed coroutines easily and because most of our programmer aren't really C# programmers but Typescript programmers. We found it to be a delight to work with, however, but realistically I don't think there will a reason to adopt Go very often if you're big on C#/Java. At least unless the landscape of the talent pool changes into Go orientated, as it'll typically be much easier to hire and onboard people from C#/Java.

                                        • neonsunset 11 months ago
                                          This sounds rather crazy and unrealistic, as C# with its task system and threadpool implementation is strictly better at massively concurrent and parallel applications.

                                          Go in general is a poor, bad language with unsound type system, significantly higher amount of footguns and much worse throughput scaling than .NET.

                                          .NET truly is in “casting pearls before swine” predicament if that’s how some of its users see it.

                                          Note that if you look at GitHub statistics - Go has already won popularity-wise because it’s not the technical merit that matters nowadays but “vibes”, which is to say no amount of bullying is sufficient until Go community stops damaging technical landscape.

                                          • Quothling 11 months ago
                                            You're not really making a lot of argumentation to back up your claims. C#'s TPL is ok, but it comes with a massive overhead compared to Goroutines where you can have millions of concurrent threads running at the same time, which is just immensely useful when you're dealing with lots and lots of data. Like gathering solar plant data from millions of inverters. On top of that Go comes with build in channels to ensure safe synchronization between your Goroutines. It's not that you cannot write concurrent code in C#, but to do so will involve a higher level of complexity as well as a much higher cost in memory usage and the risk of encountering deadlocks.

                                            For us the major advantage is that it's much more efficient to spread out the computation on multiple CPU's rather than relying on OS or thread pool threads while also lowering the risk of someone writing a bottleneck when they are coding on a thursday afternoon after a day of horrible meetings.

                                            > GitHub statistics

                                            I think Github statistics are meaningless. My github repository is 100% rust. I very rarely use Rust professionally. In fact, I've done precisely one proof of concept before we decided it was too much trouble to adopt it instead of our C++. This may change in the future if the Rust "community" matures. In any case I mostly look at job "statistics" for my area of the world and while Go has been adopted at some of the places I might want to work, it's still a drop in the ocean of java/c#/php/python.

                                      • banashark 11 months ago
                                        F# 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-typin...

                                        Official docs: https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...

                                        • zogrodea 11 months ago
                                          That'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 ).
                                          • fire_lake 11 months ago
                                            I sort of wish the defaults were flipped in F# - everything is inline by default.
                                            • Smaug123 11 months ago
                                              Could 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 11 months ago
                                                Yes I would prefer compile-time duck-typing everywhere by default.

                                                Well, I think that I would! It probably has unintended consequences.

                                          • kevingadd 11 months ago
                                            In 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.

                                            • tgma 11 months ago
                                              That's a single function pointer not an interface which is a named set of function pointers.
                                              • SideburnsOfDoom 11 months ago
                                                An 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 11 months ago
                                                  Did you just misquote me and "well, actually'd" your own paraphrase? I never used "one or more" :)
                                                  • SideburnsOfDoom 11 months ago
                                                    What 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.

                                              • beart 11 months ago
                                                I'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 11 months ago
                                                  What 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 11 months ago
                                                    In 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.
                                                    • wvenable 11 months ago
                                                      But then, in practice, why is it a problem to be explicit?
                                                • swisniewski 11 months ago
                                                  Extension 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-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.

                                                  • UglyToad 11 months ago
                                                    I'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 11 months ago
                                                      If 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);
                                                          }
                                                      • UglyToad 11 months ago
                                                        My aim is to use dependency injection to inject the minimal dependency and nothing more. Versus the grab bag every interface in a medium-complexity C# project eventually devolves into.

                                                        I've had this on my blogpost-to-write backlog for a year at this point but in every project I've worked on an interface eventually becomes a holding zone for related but disparate concepts. And so injecting the whole interface it becomes unclear what the dependency actually is.

                                                        E.g. you have some service that does data access for users, then someone adds some Salesforce stuff, or a notification call or whatever. Now any class consuming that service could be doing a bunch of different things.

                                                        The idea is basically single method interfaces without the overhead of writing the interface. Just being able to pass around free functions but with the superior DevX most C# tools offer.

                                                        I guess I want a more functional C# without having to learn F# which I've tried a few times and bounced off.

                                                        • neonsunset 11 months ago
                                                          If 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 11 months ago
                                                            The 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 11 months ago
                                                        In 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.

                                                        • chris_wot 11 months ago
                                                          Isn't this just a peculiar brand of parameterized type?
                                                          • agumonkey 11 months ago
                                                            So it's a kind of interface narrowing ?
                                                            • cryptonector 11 months 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.

                                                              • issafram 11 months ago
                                                                Eh 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 11 months ago
                                                                  It would be useful in testing, mocking (faking) sealed classes etc
                                                                  • masklinn 11 months ago
                                                                    You 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.

                                                                • WhereIsTheTruth 11 months ago
                                                                  You 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"
                                                                      }