9 ms·
It's interesting to hear that Guido is now excited by types. A few years ago his opinion was still that types can't catch most problems. I think I will listen t
by jstimpfle 10y ago
It's interesting to hear that Guido is now excited by types. A few years ago his opinion was still that types can't catch most problems. I think I will listen to that interview.
Personally I think types are over-hyped for most (profane) code.
The problem with complicated and/or specific types (which promise to catch more errors) is, in one word, coupling. They create serious dependencies across the whole project or even across project boundaries.
The other problem is: one functions-guy's URL is the next function-girl's string. And all this up- and downcasting usually creates considerable noise. Often overzealous typing is painting oneself in a corner, creating more pain then relief. It's like creating these totally arbitrary object-oriented inheritance-based taxonomies which might make some sense in one place of the code but totally break down in the next place.
But speaking about primitive types like int and str, or maybe list of int - most function arguments can accept only one of those for the code to make sense. So strategically placed type annotations can help reduce some boilerplate and put some useful barriers for error search there. While the simple-types cases are also the easy ones - the first thing you'll notice while testing is usually that you put a str where an int was expected.
- deleted 10y ago[deleted]
- adwn 10y ago> The problem with complicated and/or specific types (which promise to catch more errors) is, in one word, coupling. They create serious dependencies across the whole project or even across project boundaries. With dynamic typing, you'll still have this coupling, but now it's unchecked and (in practice) undocumented.
- jstimpfle 10y agoMost parts of the code don't actually care about all aspects of the type (or the value), but only that it's an object that they can hand to the next function. Or they only care about a subset of the value's properties (like it being a string, not necessarily a URL). For example, in Haskell there is the filter function: filter :: (a -> Bool) -> [a] -> [a] filter _ [] = [] filter pred (x:xs) | pred x = x : filter pred xs | otherwise = filter pred xs It doesn't actually care what (the type of) a is. This is one of the showcase examples of the power of Haskell's typesystem. (And admittedly it works nicely in this case). But you can have that easily with no typing at all. def filter(pred, xs): return [x for x in xs if pred(x)] So, dependencies avoided and it's not a huge source of bugs. While in the typed case, a system is needed to prove that the properties that get input come out again (or possibly a function of these properties). That quickly gets quite involved for more advanced cases. Just look what the average Haskell code looks like, how many language extensions are typically needed, how crippled the code typically is (libraries dictating exactly what properties the client must be able to statically precompute). Most of the Haskell community seems to agree that the end-goal would be a practical system for dependent types (the unification of types and values, i.e. no types at all ;->). For example, there are some libraries trying to capture the construction and execution of valid SQL queries in the type system. To understand these libraries without doubt a genius-level IQ is required. But the client code is still basically an unreadable mess. And the libraries are not reusable for cases where the database schema isn't known at compile time. The static type system actually prevents code reuse -- it makes the code really inflexible.
- watwut 10y agoThese super general very reusable functions are just a small part of code. Most of it depends on how arguments objects looks like exactly. It all becomes much harder to maintain the moment the project grows bigger or you are new to it. Types make it much easier to figure out what is allowed as a parameter where and what I am expected to do in order to make the system work. When I switched from typed to untyped language, it was like having hands cut off.
- jstimpfle 10y agoYou are making my point, I'm very much in favor of primitive or very simple types. More complex types typically have a bad cost to benefit ratio. And it's quite doable to get by even without these simple-type annotations. Just put some assertions at strategic locations. Of course there still is some amount of debugging involved. On the other hand development was much cheaper at other places, and once it works you can be reasonably confident that an int will not turn into a string overnight.
- vosper 10y agoThe problem with assertions is that sometimes they mean people get woken up in the middle of the night for an issue that would have been caught hours/days/weeks before by a statically-typed language. If you don't have 100% test coverage in Python you just can't rule that possibilility out. I say this as a full-time Python engineer and I've been woken up at 3am by things that slipped through testing and would have been caught in (say) Java or C#. Now, it is arguable whether it's worthwhile to take that chance to make development quicker and more flexible. I can definitely see that. Also, if you have to wait a long time for the compiler then that might actually be a loss in aggregate. It does suck trying to refactor large Python codebases though. Running and fixing the tests over and over until errors go down to zero is no fun.
- crdoconnor 10y ago>The problem with assertions is that sometimes they mean people get woken up in the middle of the night for an issue that would have been caught hours/days/weeks before by a statically-typed language. If you don't have 100% test coverage in Python you just can't rule that possibilility out. I say this as a full-time Python engineer and I've been woken up at 3am by things that slipped through testing and would have been caught in (say) Java or C#. For me, carefully placed assertions combined with a comprehensive (but not 100%), test suite nearly always means A) catching the exception with a test or B) a clear production error that indicates a problem with the production environment. I've been woken up plenty by poorly written Java that has had its type system intentionally weakened by terrible coders, meaning lots of detective work. It's a fallacy is that dynamic automatically means weak and static automatically means strict. Assertions aren't just useful in dynamically typed languages. Java would benefit from using them as well.
- erokar 10y agoI agree. The benefits of static typing (catching trivial errors, documentation) usually do not outweigh the costs. https://www.infoq.com/presentations/dynamic-static-typing https://www.infoq.com/presentations/dynamic-static-typing
- jstimpfle 10y agoWatched the whole thing. Very articulate speaker, thanks! Btw. going through your older posts > Besides immutability, what are the advantages of using namedtuples instead of dicts? fixed attributes. And they are ordered! :-) Makes sets / db-style programming so much simpler: for user, name, account in users: ... You can do that with conventional tuples as well, but they can't match the introspectability and the value for documentation.
- alexvoda 10y agoThat is what generics are for. When you want some code to be applicable to any type of input it can be applied to.
- spion 10y agoYou should look at modern structural type systems that support generics. They have none of the flaws you're describing here. TypeScript is one such example. For example if you define a TypeScript interface interface Serializable { toJSON():string } then any object that has a toJSON() method that returns a string is automatically accepted as a Serializable. You can even define such anonymous interfaces inline without giving them a name: function frob(obj:{toJSON():string}) { // do stuff } Basically, you get statically checked duck types and no coupling.
- jstimpfle 10y agoI think Go's story is similar. I know neither TypeScript nor Go, but "no coupling" isn't so simple as removing "implements" qualifiers. Basically the only way to really eliminate coupling that I see, is passing explicit function dictionaries, and retract the idea that there is only one valid implementation of any given (type, concept) tuple. It's very bad if all interface implementations have to be physically coupled with the implementation of the class itself (as I think the case is with Java). It's still at least unflexible sometimes if you are free to put the implementation anywhere (at the class definition site, at the interface definition site, at an independent site) but the system enforces that there is at most one implementation of each interface (this is how it's done in Haskell). This strongly discourages alternative implementations because on has to circumvent the machinery. I don't think there can be a system that automates away at least part of the boilerplate that is dictionary passing, while still retaining the flexibility to choose alternative implementations. The Haskell (maybe also TypeScript?) way can be very nice if you can be sure that there can be only one valid implementation of a concept. However, most concepts actually don't lend themselves to a single implementation. Most concepts aren't mathematically pure enough for there to be one and only one canonical implementation. Right now I can think only of a few where it's almost always fine to use the default implementation instead of being explicit, because one doesn't care much about the result - like "Show" for debug output. Take for example JSON: I can think of a thousand ways to create some JSON output from my global data. To get them all in the bag, you would start introducing phantom types and what not, and implement toJSON on those. Really nothing gained at this point, it's only losing some readability at the use site. Given some run-time introspection or metaprogramming on the other hand, a lot of flexibility can be won back. Here is an example of my preferred way to do it (metaproramming) https://github.com/jstimpfle/python-wsl/blob/master/wsl/parse.py#L395 https://github.com/jstimpfle/python-wsl/blob/master/wsl/pars... Just spend 5 lines to get the data in shape for the task at hand. The original function dict, having a specific layout convention, and containing functionality that is just not needed here, is specified here (informally) https://github.com/jstimpfle/python-wsl/blob/master/wsl/domain.py#L39 https://github.com/jstimpfle/python-wsl/blob/master/wsl/doma... So 5 lines and these two locations are completely decoupled. Easy.