No, you do gain information from it: that the function takes an Iterable[Ducklike].
Moreover, now you can tell this just from the signature, rather than needing to discover it yourself by reading the function body (and maybe the bodies of the functions it calls, and so on ...). Being able to reason about a function without reading its implementation is a straightforward win.
>I already had that information. I understand my own coding style.
Good for you, but you're not the only person working on the codebase, surely.
>My function bodies are generally only a few lines, but my reasoning here is based on the choice of identifier name.
Your short functions still call other functions which call other functions which call other functions. The type will not always be obvious from looking at the current function body; often all a function does with an argument is forward it along untouched to another function. You often still need to jump through many layers of the call graph to figure out how something actually gets used.
An identifier name can't be as expressive as a type without sacrificing concision, and can't be checked mechanically. Why not be precise, why not offload some mental work onto the computer?
>Yes, it takes discipline, but it's the same kind of discipline as adding type annotations.
No, see, this is an absolutely crucial point of disagreement:
Adding type annotations is not "discipline"!
Or at least, not the same kind of discipline as remembering the types myself and running the type checker in my head. The type checker is good because it relieves me of the necessity of discipline, at least wrt to types.
Discipline consumes scarce mental effort. It doesn't scale as project complexity grows, as organizations grow, and as time passes. I would rather spend my limited mental effort on higher level things; making sure types match is rote clerical work, entirely suitable to a machine.
The language of "discipline" paints any mistake as a personal/moral failure of an individual. It's the language of a blame-culture.
> Good for you, but you're not the only person working on the codebase, surely.
I actually am. But I've also read plenty of non-annotated Python code from strangers without issue. Including the standard library, random GitHub projects I gave a PR to fix some unidiomatic expression (defense in depth by avoiding `eval` for example), etc. When the code of others is type-annotated, I often find it just as distracting as all the "# noqa: whatever" noise not designed to be read by humans.
And long functions are vastly more mentally taxing.
> often all a function does with an argument is forward it along untouched to another function. You often still need to jump through many layers of the call graph to figure out how something actually gets used.
Yes, and I find from many years of personal experience that this doesn't cause a problem. I don't need to "figure out how something actually gets used" in order to understand the code. That's the point of organizing it this way. This is also one of the core lessons of SICP as I understood it. The dynamic typing of LISP is not an accident.
> An identifier name can't be as expressive as a type without sacrificing concision
On the contrary: it is not restricted to referring to abstractions that were explicitly defined elsewhere.
> Why not be precise, why not offload some mental work onto the computer?
When I have tried to do it, I have found that the mental work increased.
> No, see, this is an absolutely crucial point of disagreement
No, you do gain information from it: that the function takes an Iterable[Ducklike].
Moreover, now you can tell this just from the signature, rather than needing to discover it yourself by reading the function body (and maybe the bodies of the functions it calls, and so on ...). Being able to reason about a function without reading its implementation is a straightforward win.