What you point out is a developer error. Mixing variable types in a dynamically typed language is, like generics, a code smell.
Using a dynamically typed language is all about not having to babysit the compiler, its not about playing dumb about types.
If you design your functions in a way that that function will never receive an array, you dont have to test for it. And if some idiot tries one day, it should error very loudly that something is wrong.
Too many abuse dynamic typing to the point they feel safer offloading this headwork to a compiler.
That is, they get the compiler to make sure nobody will ever send an array into that function that takes a string.
All generics do is bring back the lack of typesafety and all the manual typechecking that comes with muddying generalizing the parameter typeset.
I actually dont hate generics, they are super powerful, but I question every usage as suspicious.
I also wish people would remember human brains arent all the same. We have different ways of thinking about problems. Some people need to have a compiler to busy work, some people need to do busy work while they think.
I just wish people shitting on dynamic typed languages would remember they came to solve the mess and monotomy that arose from typesafety.
Someone mentioned they felt go was going backwards from rust, i think both languages are massive steps backward from python.
> If you design your functions in a way that that function will never receive an array, you dont have to test for it. And if some idiot tries one day, it should error very loudly that something is wrong.
Then you've moved it from a test to a runtime assertion. You're still re-implementing the type-checker, poorly.
And on top of this, it's usually not "some idiot" calling the function from a REPL. It's "some idiot's code", which may not be executed until the program is running in production somewhere if it is rare. "Oh, someone uploaded THAT kind of file? Well, let me see... AttributeError: Type SomeObject does not have attribute append. Here's a list of 47 functions that were called between the action you took and this error being raised. None of them guarantee the types going in or out, so you should probably get going waking through all this logic. You're welcome! -Python <3, XOXOXO"
It ran fine with all the file types we tested! A dependency loads the file into a custom type, but it handles this file type differently! Now it's a stinky old NoneType. :( :( :(
I love Python, and generally I agree, but the place where an error is raised can be significantly distant from the event that occurred which caused it. The lack of static typing can really make it difficult to find such bugs sometimes.
A statically typed language with compile-time type checking would flag that immediately, and chances are your IDE will show the error before it even gets to that point.
Type hints are a thing, but then you are already moving in that direction while remaining in python.
Agree that there's a big difference between the error happening at compile time vs. happening at run time in prod, but I personally find the python stacktrace much more descriptive of how we got to the error than a lot of other languages.
A certain amount of it is probably a familiarity thing, but I do usually find myself able to parse it to the point of what line in which file things started to go wrong.
Other languages can do the same, they just deliberately make the decision not to because of performance. Ruby has the same nice stacktrace (even nicer).
There was some work that took ML compile time errors and created Python-like stacktraces of how this specific error could blow up at runtime in a Python-like fashion. Students found those concrete errors with concrete values easier to reason about than abstract compile time errors.
> If you design your functions in a way that that function will never receive an array, you dont have to test for it. And if some idiot tries one day, it should error very loudly that something is wrong.
That's naïve. Unless you code extra defensively (which is the same type of overhead as excessive testing, and needs itself to be verified by testing), there's very little guarantee that type errors will result in loud failure consistently at the first point that the bad value is passed (because functions that don't do unnecessary work often pass their arguments along without doing a lot of work dependent on the very specific type.)
And, in any case, failing consistently at compile time (or, with a modern code editor, at writing time) with static type checking is better than failing at runtime, even if it is loud and consistent.
Of course things like mypy and TypeScript (especially, in the latter case, because it powers tools that work pretty well when the actual immediate source is plain JS, or JS with JSDoc comments from which type information can be extracted, when consuming libraries with TS typings) mean that some popular dynamic languages have static type checking available with more expressive type systems than a number of popular statically-typed languages.
In Python, it’s entirely idiomatic to take different types of input and do different things. Look at the pandas dataframe constructor or just about anything in the datascience ecosystem (e.g., matplotlib). Look at the open() builtin—the value of the second string argument changes the return value type. Things aren’t much better in JS land where a function might take an arg or a list or a config object, etc. We see dozens of type errors in production every day.
For what its worth, the datascience ecosystem is probably the worst place to get general idioms from. They tend to err towards their own domain language and build stuff for people who arent programmers. i.e. for caller flexibility instead of maintainability.
Besides that, i only said it was a code smell, i dont debate that its useful sometimes. Just that its a signal to be careful, check you really need itor there is t some other constraint you can break.
My only advice is that narrowing your parameter types at the earliest opportunity is a good practice and results in much easier to understand code.
I kinda wish Python took the Rust approach of having multiple factory static-methods, rather than one constructor that does different things based on what arguments you pass in.
Yeah, it seems not very idiomatic. I regularly see "senior engineers" doing I/O, throwing exceptions, etc in constructors. I staunchly believe a constructor should take exactly one parameter per member variable and set the member variable. For conveniences, make static methods that invoke that constructor. This will make your code far more understandable and testable (you can initialize your object in any state without having to jump through hoops). This is language agnostic advice, but some language communities adhere to it better than others. Python is not very good about this.
I wonder if dataclasses might change this. They provide an __init__ that does what you describe, and though you can supply your own __init__ instead, classmethods seem the easier way to add custom initialization logic.
Yeah, it would be a very good thing for the Python community if dataclasses became idiomatic and vanilla classes came to be regarded as a code smell. Basically what every language should have is the concept of a "struct" or a "struct pointer". Constructors imply that there is one right way to construct an object which is rarely true except in the one-param-per-member-field sense.
"Convenience constructors" (i.e., static methods for creating instances from a set of parameters) are fine, but should often not be in the same module (or perhaps even the same package) as the class anyway. For example, if you have a Book class and it has a convenience constructor for creating a book from a database connection string and a book ID, this is probably reasonable for certain applications, but if it's likely that others would want to use your book class in non-database applications, then it would be a travesty to make the non-database application take a dependency on sqlalchemy, a database driver, etc (especially since database drivers tend to be C-extensions which may or may not be difficult to install on various target platforms).
Im sorry you feel like i was shitting on statically typed languages.
I can assure you I do beleive they have their purpose and place in the world where raw performance or closer to metal abstractions can take place.
Personally, my brain much prefers to use a dynamic type system to infer what i mean leaving the thinking part up to me.
I just find defining a string is a string when im only ever going to put a string in there kind of redundant.
There are other arguments about readability, refactorability, and similar and I think they have some weight in large doverse systems with many many teams interacting.
Personally, that isnt a large issue for me. Im working at the scope I can maintain a standard and a level of consitency that alleviates these problems to the point the type safety is simply not wareanted from a return on investment point of view.
I do not, for instance, think the kernel or chrome should be rewritten in javascript. Despite what some react developers seem to think.
> I just find defining a string is a string when im only ever going to put a string in there kind of redundant.
There are 2 problems with this kind of thinking.
1) you aren't going to be so sure. Humans are imperfect and as codebase grows, everyone commits such errors. Eg comparing a string to int without converting it to int in python. It always returns false (IIRC) and it is hard to detect where the problem is.
2) Most type systems aren't that verbose. I guess you got the impression from java / mediaeval C++. But most statically typed languages have local variable type inference. OCaml / Haskell / F# / Swift etc.. have varying degrees of type inference. At this point, types in method signatures, in languages that require it, serve as documentation. And many of these languages are terser than python/javascript.
1) I work on a typescript codebase for work. There is a real need for types in this case because the definitions are about 3 layers of abstraction away from their use. It drives me up the wall because I know when you keep definitions close to use, or have some basic conventions this problem melts away. At least to the point of diminishing returns where types are no longer relevant.
So, i guess over the years ive been caught out by int + string = 0 all of like, 1-2 times in production, maybe a few more while in dev. Either way its rare enough for me to call FUD on the argument. Stop mixing types and this really doesnt happen. To be fair to your point, at work, i know of a recurring error in production where this is almost certainly the same class of error. Nobody can be bothered fixing it because its too hard to track down and the cost benefit isnt there.
2) Type inference is problematic for me. Doesnt it just open you up to type errors of a different class?
2.5) terse code is not the point, I find rust cryptic and dense to say the least. I prefer something like go wgere readability is a priority over density.
> 2) Type inference is problematic for me. Doesnt it just open you up to type errors of a different class?
It's at compile time, so not really? It will tell you exactly what it expects vs what you provided at compile time. These kind of languages read like dynamic languages (for the most part) but they are completely static.
What you point out is a developer error. Mixing variable types in a dynamically typed language is, like generics, a code smell.
Using a dynamically typed language is all about not having to babysit the compiler, its not about playing dumb about types.
If you design your functions in a way that that function will never receive an array, you dont have to test for it. And if some idiot tries one day, it should error very loudly that something is wrong.
Too many abuse dynamic typing to the point they feel safer offloading this headwork to a compiler.
That is, they get the compiler to make sure nobody will ever send an array into that function that takes a string.
All generics do is bring back the lack of typesafety and all the manual typechecking that comes with muddying generalizing the parameter typeset.
I actually dont hate generics, they are super powerful, but I question every usage as suspicious.
I also wish people would remember human brains arent all the same. We have different ways of thinking about problems. Some people need to have a compiler to busy work, some people need to do busy work while they think.
I just wish people shitting on dynamic typed languages would remember they came to solve the mess and monotomy that arose from typesafety.
Someone mentioned they felt go was going backwards from rust, i think both languages are massive steps backward from python.