On Tue, 12 Mar 2013 09:57:44 -0700 "H. S. Teoh" <hst...@quickfur.ath.cx> wrote:
> On Tue, Mar 12, 2013 at 12:28:07PM -0400, Nick Sabalausky wrote: > > On Tue, 12 Mar 2013 16:02:24 +0100 > > "TommiT" <tommitiss...@hotmail.com> wrote: > > > > > On Tuesday, 12 March 2013 at 13:44:35 UTC, Nick Sabalausky wrote: > > > > On Tue, 12 Mar 2013 12:51:03 +0100 > > > > "TommiT" <tommitiss...@hotmail.com> wrote: > > > > > > > >> On Tuesday, 12 March 2013 at 02:39:06 UTC, TommiT wrote: > > > >> > struct S1 implements A2 { > > > >> > void foo() { } > > > >> > void bar() { } > > > >> > } > > > >> > > > >> That's not good. Types shouldn't have to explicitly say that > > > >> they implement a concept. > > > > > > > > I *strongly* disagree. A much as I love ranges (for example), > > > > their duckiness is the one thing I consider to be a huge > > > > mistake. > > > > > > The problem with having to explicitl specify that a type > > > implements a certain concept, is the resulting strong coupling > > > between the concept definition and the type. This prevents "happy > > > accidents" like the following from happening: > > > > > > Alice and Bob write libraries without knowing anything about each > > > other or each other's code. Alice implements the following in her > > > library: > > > > > > concept IntegerLike { > > > ... > > > } > > > > > > void foo(IntegerLike N)(N n) { } > > > > > > Bob implements the following in his library: > > > > > > struct SafeInt { > > > ... > > > } > > > > > > Later Bob realizes that Alice has written this cool function foo > > > which accepts his type SafeInt as an argument because SafeInt > > > just so happens to fulfill the requirements of the IntegerLike > > > concept defined in Alice's library. > > > > > > Although, the majority of concepts should come from the standard > > > library. > > > > "Happy accidents" is nothing more than another way of saying "Shit > > fucked up, but by pure dumb luck there was no damage". It's an > > absolutely *terrible* thing to encourage and design for. You may as > > well just go dynamic all the way, a la ActionScript 2 or Python - > > it's all the same "let random things happen by accident and blindly > > hope it just happens to turn out correct" philosophy. > > > > Design-by-accident is an anti-pattern. > > > > To me more specific, the problem with duck typing is that it falsely > > assumes that name+signature uniquely defines semantics (which is > > clearly not a valid assumption). Avoiding accidental screwups under > > duck typing *is* feasible if there's only a few well-known duck > > types that are ever in play (ex: our entire list of available duck > > types is a handful of phobos-defined ranges and basically nothing > > else). But it does not scale: The likelihood of accidental fuckups > > is multiplied with each additional duck type in existence, with > > non-stdlib duck types carrying a greater "accidental fuck up" > > weight. > > I see it from another perspective: I've had to deal with proprietary > libraries that defined types that couldn't be used with a particular > container type, just because the container type expects the item type > to implement a particular interface, but it doesn't. But actually, it > *does* have the requisite members to implement that interface; it just > didn't *say* it implemented that interface. So there's the need for > Yet Another Useless Java-style Wrapper Class just to work around this > nonsense. Duck-typing solves this problem. > Duck-typing solves that problem at the cost of potentially introducing silent bugs. But who says it can only be solved that way? The problem is trivially solvable *without* the downsides of duck-typing. Just make the ducks explicit instead of implicit. All you need is something analogous to "cast": For example, an "(a|A)ssumeImplements" wrapper. Or maybe even just make it an extra feature of "cast": import std.typecons : assumeImplements, AssumeImplements; concept IBob { Some syntax to require "void bob()" } struct BobType : IBob { void bob() {...} } struct AliceType { void bob() {...} } // *Explicit* duck-typing alias AssumeImplements!(IBob, AliceType) AliceTypeIBob; void useBob(IBob ib) {...} // Or whatever syntax void main() { BobType b; useBob(b); // Ok AliceTypeIBob ab; useBob(ab); // Ok AliceType a; useBob(a); // Fails // Ok, *explicit* duck-typing useBob(assumeImplements!IBob(a)); // Maybe ok, semi-explicit duck-typing useBob(cast(IBob)a); } > Of course, full-out ducktyping has its own problems, like you said; > but there needs to be a way of rewriting APIs such that you could say > "type T doesn't implement interface I right now, but actually, if you > rewrite T.A to T.X and T.B to T.Y, then T implements interface I just > fine". Though with D, I suspect this may actually be possible via > alias this: > > struct RewireInterface(T, Interface, Tuple!(string,string)... > rewrites) { > T t; > alias t this; > foreach (rewrite; rewrites) { > alias rewrite[0] = rewrite[1]; > } > } > Interesting.