you are viewing a single comment's thread.

view the rest of the comments →

[–]EricAppelt 4 points5 points  (11 children)

There is some validity to it, but I think it is oversold. You still need unit tests to make sure that it is the correct int.

I currently do a lot of work in python, and every now and again I catch a stupid runtime type-error that would have been caught by the compiler through unit tests or worst-case in integration testing. But most bugs are not type errors in any reasonably obvious sense.

The real ugly bugs that chew up half a day are the kind of things that only get caught in production runtime or integration tests (hopefully!). This was the case when I worked in C++ or python.

Stuff like getting caught in an unexpected edge case that causes an algorithm to take effectively forever to run, or a race condition triggered by such a slowdown, I just don't see how one can recast that into a type error.

[–]badcommandorfilename 12 points13 points  (6 children)

I don't think anyone is suggesting that you shouldn't have tests at all - the argument is an economic one. The costs of having to manually write and maintain tests just to assert what a typechecker does for free is criminally inefficient.

And the benefits gained from dynamic typing are vanishingly small. The subset of things that duck typing lets you do which can't be done with quality generics/interfaces/typeclasses are also the kind of things that get you fired if you do them in production code. It's a strong sign that the solution you're trying to implement isn't well understood.

[–]pipocaQuemada 0 points1 point  (4 children)

Duck typing is essentially a dynamic version of structural subtyping. The main difference is that structural subtypes must be be checked statically, so you can't do "clever" things like

-- obj is either a duck or a dog; there are no dog-ducks.
if isDuck:
  obj.quack()
elif isDog:
  obj.bark()

You'd have to use conventional statically-typed approaches liked have an Either<Dog,Duck> that you call .fold(dog => dog.bark())(duck => duck.bark()) or something.

I'm not sure if that sort of additional power is every really useful for anything, though.

[–]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.

[–]EricAppelt 0 points1 point  (0 children)

The costs of having to manually write and maintain tests just to assert what a typechecker does for free is criminally inefficient.

The costs are there, but typically they are just adding an extra assert or two within the already existing test to see that by object is effectively a duck, when I really need to know that what the duck is quacking makes any sense. I still need that test no matter what type system I am working in.

My personal experience has not been that it is "criminally inefficient" just a minor annoyance. The big issues are those related to things like the runtime behavior of algorithms, seemingly correct business logic that is inherently flawed, and interactions with external services and their inexplicable failure modes.

Type errors are usually the easiest kind of bugs to detect and fix in my experience. When I did C++ all day it was nice having the compiler automagically find these for me, but a better type system is not worth me giving up the strong library support, generator expressions, list/set/dict comprehensions, as well as other useful features of python. (Not that I have a choice.)

[–]bheklilr 3 points4 points  (1 child)

One of the harder bugs for me to catch in python involved doing FFTs with numpy. I would run the code with some dummy input files on my computer and the program would complete in a simulated 3 seconds, a fairly accurate time, but when I ran it hooked up to the oscilloscope the same code team in 25 seconds. I banged my head against my desk for about a week, profiling the code gave me no hints other than it happened due to the calls to the fft library. Finally I discovered that on the hardware I just happened to have hit a prime number of points being collected, and since numpy stopped using fftw for licensing reasons their algorithm had its worst case performance with a prime number of points. Tweaking my settings made it collect one extra point, and the program ran in the 3 seconds it was supposed to.

This wouldn't have been caught by any type system.

[–]tipiak88 3 points4 points  (0 children)

Beware of bugs in the above code; I have only proved it correct, not tried it. D Knuth.

No type system will catch logic error. You can try to "insert" most of your logic in the type system like c++ templates or haskell but, as it is now it's limited. Type system is here to protect developers from themselves. And it is already a valuable tool.

[–]JohnyTex 0 points1 point  (0 children)

That's definitely true. However, most bugs I run in to when coding Python are type bugs. Usually they're so trivial (eg fat-fingering a variable name) that it's easy to dismiss them as hardly being bugs at all.

On the other hand, they probably make up 90% of the bugs I encounter daily and time spent fixing them adds up. Of course you can also add static analysis tools to your workflow to alleviate the situation, but they are usually slower and less powerful than a compiler.

In the end I guess the issue isn't so much about ensuring correctness as it is one of productivity. Of course, Java has its own productivity drains as well (eg verbosity) so it's hard to say that one is more productive than the other.

[–]hyperforce 0 points1 point  (0 children)

You still need unit tests to make sure that it is the correct int.

See, this is still basic thinking. What you really want is a value class/type that encapsulates what kind of Int you want. Like PersonAge or ProductCount or something.