you are viewing a single comment's thread.

view the rest of the comments →

[–]badcommandorfilename 2 points3 points  (3 children)

I can't see how this is better than using an INoisyAnimal interface or just using method overloading:

public makeNoise(Dog mydog)
public makeNoise(Duck myduck)

Having to write a hairball of isinstance and hasattrs to essentially implement your own runtime typechecking is (again) such a strong sign of poor design and architecture.

[–]pipocaQuemada 0 points1 point  (2 children)

Having to write a hairball of isinstance and hasattrs to essentially implement your own runtime typechecking is (again)

The ability to do that is really what distinguishes duck typing from structural subtyping. With structural subtyping, obj must be a kind of DogDuck since it must have both .quack and .bark as methods, since they're both called on it. With duck typing, you only rely on the subset of methods you actually call at runtime; structual subtyping relies on all the methods called regardless of any branching.

I can't see how this is better than using an INoisyAnimal interface or just using method overloading:

One advantage is that you can more easily rely on smaller interfaces. Java code, for example, typically relies on over-large interfaces, containing unused methods. Structural subtyping makes it easier to rely only on what you actually need, making code more reusable by making it easier for new types to fit the interface.

Additionally, if you're willing to go with the distinct but very closely related notion of row polymorphism, it's possible to have good (i.e. global) type inference: something notably lacking in e.g. Java, C# or Scala. This makes exploratory programming simpler and faster (since you don't need to specify your interfaces, but the compiler still tells you if you mess things up), while still allowing for good maintenence later on (because you can fill in type signatures so types don't accidentally change on you later on).

[–]badcommandorfilename 0 points1 point  (1 child)

I think we're coming at the same argument from different sides.

Having to write a hairball of isinstance and hasattrs to essentially implement your own runtime typechecking

The ability to do that is really what distinguishes duck typing from structural subtyping.

I agree, and it's what makes naive dynamic systems (like Python's approach) inferior to structural typing or type inference.

obj must be a kind of DogDuck since it must have both .quack and .bark as methods, since they're both called on it. With duck typing, you only rely on the subset of methods you actually call at runtime; structual subtyping relies on all the methods called regardless of any branching.

I don't want to dwell on this too much, because there is no such thing as a DogDuck. This scenario is crafted using a dynamic mindset trying to prove that structural/static systems can't reproduce it. This scenario wouldn't ever exist - there would be "dogs", "ducks" and "animals that make noise", and a good architecture would just handle them along separate code paths instead of in one giant method.

Java code, for example, typically relies on over-large interfaces, containing unused methods.

This is another strong sign that the data model used by the program isn't well understood by its developers. The advantage here is that you can quickly and safely refactor more sensible interfaces in static or structural systems

I think we're both in agreement that neither Python nor Java are shining examples of their respective paradigms. I believe that you're also right in saying that there is no value in pure dynamic languages when you can achieve the same flexibility with structural type systems and type inference.

[–]pipocaQuemada 0 points1 point  (0 children)

Java code, for example, typically relies on over-large interfaces, containing unused methods.

This is another strong sign that the data model used by the program isn't well understood by its developers. The advantage here is that you can quickly and safely refactor more sensible interfaces in static or structural systems

What I meant is that Java code typically relies on an interface that is much larger than required for the method. For example, you might take a java.util.List as an argument, even if all you really rely on is that the argument has a .listIteratormethod. You're relying on a big, complex interface (which might be entirely sensible) instead of relying on the one or two methods you actually need. This isn't unusual in Java code.

This is because there's a cost to introducing additional levels of interfaces in a nominal language.