Type hints – a mediocre programmer's reaction (2015)
mail.python.org
mail.python.org
Python API are defined solely by how the API uses its argument. The requirement is already encoded as far as the computer is concerned, except the check are by default all done at run-time. A Python compiler could work its way backward to try to verify them at compilation time. Making them explicit would solely be as a helper for the programmer, which might as well just be in straight documentation, which can ba as brief as the name of the parameter.
It's all a feature, and this is being actively exploited in code, providing exceptional flexibility. Its no worse than other language with type inference: their types can be extremely complex and reading them would not help most of the time. You're better off with a succint, high-level description, like a URL-like string, a file-like object, an object indexable by such and such.
Duck typing isn't a bad thing. In fact, it's great. You just need to annotate the ducks instead of the types. For a great example of how to do it, look at Haskell's type classes.
(In fact, Haskell's easyness of integrating generic code is visible even to a beginner, but I never notice that this is because the entire language is based on duck typing.)
Type classes absolutely do not create this kind of situation, as they are duck typing. But modules do create it, so it happens both on Haskell and on Python.
Now, in practice you use both, basic classes and library specific ones. And yes, if you want to create some behavior, you'll need to implement the specific code that enables it. You do not escape from that in Python either.
You can actually escape dependency hell for most APIs. That's the Unix culture of using plain text (read: very simple data types -- in C that would be arrays and structures of integral types) for communication.
Of course that won't work for libraries that provide transformations on parameterized structures. But let's face it, there's no point in depending on a Monad library just to save two lines of pure code.
In addition you'd need an IRO (interface resolution order) if you truly intended to prove everything would work
To say nothing of accidentally implementing an interface and getting an error because if it
Doesn't sound like my kind of python...
You step back a layer of abstraction. Maybe it implements seek, maybe it doesn't. Either way it implements has_seek.
Interface based typing may be a better generic term.
This is a little a symptom of lack of overloading (and lack of static typing therefore), but should be not hard to solve buy disjunctive hints like this one: [a]|(a,b) (and in case of Python, looks like Union[] would do?).
The URL case is also pretty much the same and for example in Java you would have to write a different method signature for each such case and that would be natural. Again, disjunctive hints would solve this.
Given that, I do not know what is the complaint really here. Perhaps that syntax is horrible, or it is hard to know what are the actual types in Python?
{a:b} for mappings from a to b
(a,b) for tuples of a and b
[a] for iterables of a
a | b for unions of a and b
Then factor out that nasty common thing and you end up with:
Optional[ {basestring: StringThing}
| [(basestring, StringThing)]
]
StringThing = (basestring, Optional[basestring | file]])
| (basestring, Optional[basestring | file]], Optional[basestring])
| (basestring, Optional[basestring | file]], Optional[basestring], Optional[Headers])
Headers = {basestring: basestring}
| [(basestring, basestring)]
It's not inherently complicated nor does it have anything to do with duck typing, it's just that the syntax chosen is awful.I wanted to have a generic protocol that could have some logic of its own without enforcing you to subclass it. I would regret the decision so much, if I wasn't busy regretting other decisions even more.
Just enforce a type that you accept that's large enough to be used by many and convertable from other common types you want to use.
I swear, soon they'll classify programmers urge to write as generic and extendable as a mental illness.
But then it wouldn't be "pythonic". To be frank though, I think being too generous with your inputs is just asking for maintenance hell. I don't see why requests feels the need to allow data in so many different formats when one or two + conversion functions for anything else would be sufficient.
""" Be conservative in what you do, be liberal in what you accept from others (often reworded as "Be conservative in what you send, be liberal in what you accept"). """
(Security is non-trivial and mostly there are very good reasons that things are exactly as they are in such protocols.)
> At this point, you may want to just stop caring about the exact type.
> Part of the point of gradual typing is that you can short-cut a lot of
> this. And quite frankly, this isn't really helping anything. Just skip
> it and say that it's Union[Mapping, Iterable, None].
"This change doesn't catch any of the bugs anyone was going to hit (no-one is passing a callable to this parameter), and it doesn't catch any of the bugs someone might actually hit (getting the tuple elements in the wrong order, for example). At this point the type signature is worse than useless."
Type hints are going to be awesome for nailing down the behavior of the core parts of your business application. I think of it more like writing good tests than anything else.
Like everything else in development; 80% of the problem is choosing the right tool for the job.
You're right, though. Python doesn't seem to be made for applications that would benefit from being provably compiled.
For example, Perl6 has Subset (types), which is a great feature. You could make a subset type of Str (a built-in type) which contains only strings that match a given pattern (e.g. URLs, user-ids, or shell-safe strings) and still benefit from all generic string code.
This has probably been driven by languages that showed that static typing doesn't have to be as inconvenient as Java, like Haskell or Rust, but the proponents also seem to argue for inferior type systems.
Take for example a spec for a "Set-Cookie" header syntax. It can be described as "https://tools.ietf.org/html/rfc6265#section-4.1.1", which would be a rather unwieldy thing to show, looking similarly to the pythonic polymorphic argument described in this message.
Type hinting (and strong typing) helps machines 100% of the time, and users some of the time. It's helpful if your API has an interface that is easy for a human to understand, even if you describe your data as 'a URI', "truthy" or even "Any", even if their shapes are not strictly defined.
For the n'th time, python is a strong typed AND duck typed language. Did you mean static?
But if you're starting fresh, it's not that hard to use keyword parameters to support a lot of different options while also having reasonable types. (Take a look at how Dart libraries do it.)
People that want to write code that runs in a Web browser are locked in to Javascript, so they're "locked in" to JS; I can't think of a platform where the only option is to use Python.
babeljs for example used to be called `6to5` so i don't think it's lockin that causes this desire, but it's certainly true that the options for clientside programming languages that are widely deployed are fairly slim. i don't think they're related though.
Convenience is API design. In a fully dynamically-typed language, there's no real downside for heavily polymorphic APIs and a lot of upside in terms of brevity and flexibility.
I believe pretty strongly that you can design great dynamically-typed APIs for dynamically-typed languages or great statically-typed APIs for statically-typed ones, but I don't think you can easily layer static types on a great dynamic API. The affordances are just too different.
One obvious challenge is that statically typed languages almost always support overloading. This makes it dramatically easier to have these kinds of polymorphic APIs while having sane types. Most dynamically typed language support overloading at all, which is where these giant union types come from when you try to cram a single static type onto what is effectively a number of different method overloads.
Wow, I'd say that's a major pain point for me—reasoning about polymorphic code with only vague guidelines:
"give me something that looks like a string".
"Well, which types are those?"
"Hell if I know! But it needs these methods, but it can also change its behavior if it doesn't have them"
"stabs self in neck because this was solved in the 60s"
Seeing a hard-to-skim polymorphic API for anything but a quick script is a massive problem for me—the time sink in just parsing its behavior isn't worth the inevitable problems that come with the edge cases at runtime.
That said, requests is great and I use it to do all my web scraping because it's super idiomatic. I had no idea the interface was that complicated, though, and it reduces my confidence in the library.
This PEP reminds me of typed racket: pretty awesome, but a completely separate language to write with separate considerations.
Switching gears and thinking about code validation for a minute, I agree, protocol conformance checking is a much more difficult problem to resolve at compile time, at least they way Python duck types support protocol definitions.
I tend to see it like: type is what this object actually is, and protocol is how we interact with it.
If ever there were a language where you'd want to be looking at protocols and not concrete types most of the time, it would be Python.