Slightly related question: why do people love Python so much? Or, any dynamically typed language? There's almost nothing that has frustrated me more in a professional setting than trying to figure out what some dynamically typed code is doing and ensuring I don't break anything by making changes.
- Common stuff (opening files, dealing with databases, string processing, math, regexps) is very easy. Sane defaults.
- Works well with the existing C ecosystem. It's not a "rewrite everything in X" language.
- It's everywhere, most machines run Python just fine.
- It doesn't treat Windows like a second-class citizen.
- It doesn't force upon you a programming model that you might or might not care about. It's not a puzzle language where everything is a monad, or everything is a class, or everything is some weird widget. Write the damn code.
- I'm personally not a fan of the language itself, but it's mostly a bog-standard algol if you don't poke it too much. It's relatively standard, people just get it even if Python isn't their main language.
- Very personal point, but it's the only language I can edit with a plain text editor. Think nano, vi, Windows Notepad, that kind of thing. I don't know what to tell you, maybe it's my tiny monkey brain, but I can't do the same in Java or C++. Not that it's my preferred way to edit software, but I like having that option in a pinch.
At least for what I do (developer, but with a data engineering/data science flavor) the overall architecture is usually the important thing, so admittedly some downsides might hit me less.
I'd still rather write medium-to-large pieces of software (say 10,000+ LOC) in something else (Java, C#, C++, something boring like that), but it's pretty much unbeatable for small programs as far as I'm concerned.
> It doesn't force upon you a programming model that you might or might not care about. It's not a puzzle language where everything is a monad, or everything is a class, or everything is some weird widget. Write the damn code.
Yeah, I second this. There is some foot guns you'll hit in python, but the amount of foot guns I've hit in C++ and golang are 10x that of python.
The other thing is that over time, the python devs actually strive to make development easier, and they try to resolve foot guns where they can. In golang, e.g., Rob Pike is very opinionated about how software should be written, and it's carried forward in the language. Python on the other hand, recommends people be "pythonic", but only encourages it, and doesn't enforce it upon the user.
> I'd still rather write medium-to-large pieces of software (say 10,000+ LOC) in something else (Java, C#, C++, something boring like that), but it's pretty much unbeatable for small programs as far as I'm concerned.
What I've learned is the python requires unit tests, as does golang, or basically any language that can throw a null pointer err.
If you write your code modularly and have good unit tests around your code, it works amazingly well. I've been a part of good and bad large python projects. The best ones have really good unit tests around their code.
I mean, you can say 'nah' all you'd like, but there's a whole section in that link on their hostility to Java. Including choice anti-competitive gates quotes!
Can't say I'd blame any language dev who saw that, and said 'no thanks'.
That did not change the fact that Java worked perfectly alright on Windows, the same cannot be said for many FOSS languages that insist all OSes are UNIX clones.
Off topic, but I wonder if that's true? Their research division must be pretty well funded and they sure care about taste, though the limits (cost) are pretty big. Anyone know more about this?
Anecdote, but I like the Python community's emphasis on "readability counts". I did not get that from e.g. Ruby.
Python's internals are also relatively accessible and easy to work with, so it's smooth sailing once you have that need. The language starts out easy, and grows with you. Of the languages I've tried (and there are many), only Smalltalk and the Lisp family were comparable in expressiveness.
The language mostly gets out of my way and lets me do what I want. I don't feel like I have to fight the compiler (at least until I tried Mypy) or write a lot of tediously verbose boilerplate just to get out a "Hello, World!" like I did in Java.
Unlike, say, JavaScript, Python is pretty strongly typed and fails fast. The stack traces almost always point you to the exact location of the problem (unlike the JS tendency to propagate `undefined` everywhere). There aren't a lot of surprises or gotchas. I can pretty much run it in my head just by reading the code and be right most of the time. I cannot say the same for C++ or JavaScript, which naturally tend to become inscrutable without discipline.
Python is among the least-readable languages for me. The lack of whitespace and brackets around code blocks just make it look like the "wall of text" that everyone hates in emails.
And then there's the fact that Python has significant characters that are literally unreadable because they're invisible (semantic whitespace).
I never got the point against meaningful whitespace. It's indentation. You already indent blocks anyway; the only difference is that you don't need braces or begin/end.
I also did not understand your point about "lack of whitespace" around blocks. The only difference between blocks in Python and other languages is that the block start uses a colon instead of an opening brace and you don't have a line for the closing brace. Is that closing line such a big deal for you?
As someone who recently switched to opening curly brackets on new lines and did some Python coding last week I kind of agree - you either put in empty lines everywhere or you have a continuous block of code through all scopes. It's a matter of preference and you can still work with it of course, but I've learned to appreciate reducing code density.
And regarding the syntax, using curly brackets like in C-like languages only takes one more symbol per scope and the difference in readability (where a scope ends etc.) is bigger for me. Also while indentation in Python kind of works as syntax it's not ideal as it can lead to literally invisible syntax errors, and imho the syntax isn't strict enough as Python allows you to have a different indentation width in every single scope.
That said it's still a great language for little experiments. Similar to Bash, despite Bash being not the best designed language to put it mildly.
And yet I notice that you separated the paragraphs in your post using semantic whitespace rather than "BEGIN PARAGRAPH" / "END PARAGRAPH" or something similar, and it seems perfectly readable to me.
> separated the paragraphs in your post using semantic whitespace
That's not what "semantic" means.
In my post, the size of the whitespace was not semantic. I could have used one spaces, two spaces, tabs, or (as I did) a couple of newlines. All that matters is that there was any whitespace at all -- which is what most mainstream languages do.
In Python, the whitespace I chose would change the actual program.
To repeat: I do not want anything that is literally invisible to change my program. It's easy to glance at code and see "=" vs "==", but it's much harder to tell the difference between " " and " ".
A couple of newlines to create a new paragraph is semantic whitespace, as one newline or a space would not do so. Markdown (although not HN's Markdownesque syntax) even has significant trailing whitespace, which I would object to in a programming language.
> it's much harder to tell the difference between " " and " ".
Can't think of any scenario where you'd need to.
If you do mix hard tabs with spaces for indentation, which would cause readability issues with other languages anyway, it'll fail to run and point out where.
I can empathize that significant whitespace sounds off-putting at first, but after biting the bullet I think it's definitely a net positive. Indentation indicates my intent yet most languages just ignore it. For example, consider this gotcha across multiple non-significant-whitespace C-like languages:
for (i = 1; i < 11; ++i)
printf("%d ", i);
printf("%d ", i);
Or, alternatively, hunting down one misplaced closing bracket.
Also, with Python appealing to new programmers, there'd probably otherwise be plenty of beginner code with no indentation at all.
> A couple of newlines to create a new paragraph is semantic whitespace, as one newline or a space would not do so. Markdown (although not HN's Markdownesque syntax) even has significant trailing whitespace, which I would object to in a programming language.
A code block is *not* the same as a paragraph. It is the same as a part/chapter/section/subsection/subsubsection. (Think about it.) In formal writing, these are practically always clearly indicated by not just semantic whitespace but also by semantic headers with particular semantic styling as well as semantic numbering. In addition, if you you write these programatically in for example Markdown or LaTeX, you might have semantic whitespace in the markup language but you definitely will have semantic non-whitespace symbols in the markup language.
Next Quote:
for (i = 1; i < 11; ++i)
printf("%d ", i);
printf("%d ", i);
This has nothing to do with the lack of significant white space and everything to do with terrible language design. Here is the *only* valid way to write the above in Rust:
for i in 1..11 {
println!("{}", i);
}
println!("{}", i);
If you tried:
for in in 1..11
println!("{}", i);
println!("{}", i);
you would get a compiler error because the braces are required around the bodies of all control- and loop structures. Also note that a common class of bugs is avoided in Rust because in Rust the loop condition is not in parenthesis. (Nor are the conditions in `if`s, etc.)
> Also, with Python appealing to new programmers, there'd probably otherwise be plenty of beginner code with no indentation at all.
Which is to say teaching methodology for coding sucks. While you shouldn't overwhelm a beginner with linting errors from a very pedantic linter (to put it mildly), not automatically linting all beginner code for some basic issues like incorrect white space and wEirD_caPS_in_OVERLY_LOOOOOOOONG_idENTIFIERS or `l` `o` `t` `s` `o2` `f` `s2` `h` `o3` `r` `t` `i` `d` `s3` (lots of short identifiers) is, IMHO, a really bad idea.
Furthermore, actually (at least in my experience tutoring C++/Java/C#), bad white space is actually not that common for beginners after the first few lessons. I guess that most humans are used to just automatically write and type semantic white space. Therefore if they see their teacher writing code with white space they'll not only use white space themselves but use it similarly.
On the other hand the identifier issues I've mentioned above are all more common, especially too short and obscure identifiers.
> A code block is not the same as a paragraph. [...]
Right, but for Markdown they're a difference in output despite the input \n\n being just whitespace, and for the final text they have semantic effect despite being just a gap between text. I don't think the argument was whether the particular semantic meaning of paragraphs exactly matches the semantic meaning of code blocks, just that they do have semantic meaning.
> This has nothing to do with the lack of significant white space and everything to do with terrible language design
It's a problem that illustrates how indentation indicates intent, and would be fixed by semantic whitespace. I agree that if a language is going to disregard whitespace, then {...} should be consistently required to partially make up for it.
> I guess that most humans are used to just automatically write and type semantic white space
My experience has been the opposite: looking over huge blocks of completely unindented VBA/MATLAB code written by people with little prior programming knowledge, and being thankful that data science is moving more towards Python. Likely a different crowd than those being actively tutored in C++.
But yeah there are bigger, often harder to automatically fix, issues with beginner code. PyCharm has PEP 8 lints by default for things like variable name conventions, which help but are still commonly ignored.
You can add empty lines before and after blocks. The only thing that matters is the indentation. If you see code without empty lines separating different steps, that's pretty badly written Python.
I'm not a Python fan at all but still see much value in clean, DRY syntax.
Significant whitespace is a huge success even in languages that switched to it late! Almost nobody in the F# or Scala¹ community would consider to switch back to that unnecessary line noise which are explicit block markers.
Code should be indented for readability anyway (and almost nobody would dispute that)! So there is just no real value in additional block markers. In fact such block markers make refactorings more difficult than needed.
BTW: Contrary to your opinion stated in your profile "subtracting points" for writing things the majority here disagrees with is not fascist in any way but exactly the mechanism that keeps the quality of the comments here high—which in turn attracts those "smart people hanging out here" (who you're praising) in the first place. ;-)
per se, I don't mind the white spaces--but I don't want to be forced to use them. Like what you see in Julia, or Kotlin. Code is poetry. I should be trusted to organize my code according to how it reflects what it reflects.
I dislike block markers as much as forced white-space, because there is lack of freedom. I understand that this was done so that different people could easily read legacy code but I don't think this helps because despite Python zen, there way too many ways to do the same thing. Why not allow the programmer to get creative so that at least the code is elegant? Elegance is highly readable.
---
BTW: I think a good mechanism would be: "do not reward if it is not worth it" and "reward if it is." But the Fascist aspect here is: "punish if it is not worth it." Who decide what is worth what? Opinions vary. If everybody thinks alike, nobody is really thinking. Answers that earn higher points may be attractive to a majority of people, but you well know that the majority are of average intelligence (IQ is normally distributed). So mediocre quality gets upvoted, anything the mediocrity-loving majority doesn't, gets downvoted. Makes sense? The intelligent people I refer to are those who like me, question, probe, disagree, and dare to say it like it is. :) That does not mean the majority.
It's not noise, though. It protects me from common whitespace issues like careless copying/pasting, style differences between me and other developers, and IDE/OS settings.
It's also just easier to scan. Imagine if we did the same thing with math:
a * b / (c - d)
Look at that noise! We can get rid of some of those symbols, right? Let's replace parentheses with whitespace instead.
a*b/ c - d
So now it's denser and I typed fewer characters, but it's harder to scan because my eyes don't register " " as quickly and easily as they register "{".
I really wish for a functional language which prioritzes readability. I am fluent in like 10 languages but most functional languages are just noise for me. I really like the concepts but the amount of one symbol functions, useless abbreviations (seq instead of sequence) and weird syntax just turns me off.
I would like for some language which kicks the mathematicians love for noise out and embraces readability first principle.
I have a hunch that a lot of Python users didn't carefully evaluate the language on its merits, but rather, just started using it. In my own case, I was using Visual Basic 6 before Python, and mostly Turbo Pascal before that. A huge attraction of Python was the libraries -- scipy, matplotlib, etc. In fact when a lot of people say they're using "Python," they're actually referring to Python plus a large collection of libraries that came from somewhere.
It's the community. Network effect looms large.
While typing is dynamic, I rarely see this being used, perhaps with the exception of identifying empty variables with None. Most identifiers are born and die the same type. It would be interesting to see how much effect dynamic typing has on the rate of programming errors.
I suspect the same. People adopt Python because its clean syntax and REPL make the language approachable and easy to get started and then the huge library ecosystem helps you glue together (in a good way) powerful systems. But how many Python users and libraries actually use the language’s extremely dynamic runtime behavior? Something they’re paying for with a big performance penalty and surprising bugs.
Numpy uses magic methods (Python's version of operator overloading). Django uses metaclasses. Pytest dynamically rewrites assert statements using AST. I'd say it's pretty common, at least in libraries. I certainly depend on Python's dynamism when fully leveraging the REPL.
Indeed, these capabilities of the language are some of its strengths, even if we don't all trust ourselves to use them. The casual programmer doesn't know how or why numpy works, and we don't play with magic methods except out of curiosity. The people who do maintain numpy are using more formal methods to ensure its quality.
In the case of Python, the things that professional caliber programmers can use to extend its functionality, and the things that the rest of us can use to be productive, are the same language and are not hidden from one another. This is not the case in some languages of the past, e.g., you could use objects in Visual Basic but not build them without another language or eventually a special version.
Python's popularity has grown pretty steadily over decades. It retains users because of its merits and became popular in multiple niches more or less independently.
> While typing is dynamic, I rarely see this being used, perhaps with the exception of identifying empty variables with None. Most identifiers are born and die the same type. It would be interesting to see how much effect dynamic typing has on the rate of programming errors.
Dynamic typing and changing the type of a variable are orthogonal.
You can change a type with casting, coercion, transmutation, dynamic dispatch, downcasting, reflection, monkeypatching (i.e. simultaneosly using dynamic dispatch and downcasting and reflection). All of these methods are available in Rust, which is a statically typed language.
Some things that some people think are changing the type of a variable in Python is not what it seems, for example:
# Python
a = 1
a = ['h','i'] # `a: int` is unchanged but gets implicitly shadowed by `a: list[str]`
a[1] = "a" # We're combining shadowing with weird and inconsistent scoping
translates to the following in Rust:
// Rust
#[allow(unused_mut, unused_variables)]
{
let mut a = 1;
let mut a = ["h", "i"]; // Explicit shadowing
a[1] = "a"; // We're simply calling a mutating operator
}
Furthermore:
# Python
lst: Array[Any] = [1, "2", 3.0] # A heterogeneous list
for elem in lst:
print(elem);
translates to the following in Rust:
// Rust
// Below `dyn` has nothing whatsoever to do with dynamic typing.
// ... it is just fluff having to do with the requirements of systems programming.
// ... (Technically it means we're using vtables to do dynamic dispatch.)
// ... The `Box` nonsense is more systems dev fluff having to do with memory allocation.
// ... Of course in Rust, App devs often don't need to worry about such fluff because
// ... lib devs should provide ergonomic API's, but I'm doing as close as
// ... possible to a direct translation of the Pyhon code.
#[allow(unused_mut)]
let mut lst: Vec<Box<dyn Display>> = vec![
Box::new(1),
Box::new("1"),
Box::new(1.0),
];
for elem in lst {
println!("{elem}");
}
The one major difference between the Python code and Rust code above is that `Display` in Rust is a Rust trait (i.e. a typeclass) and typeclasses don't exist in Python.
Amateur coders always have flocked to the simple and unfashionable stuff. Basic used to be big for this reason. Javascript took off for this reason as well. Web designers could just copy paste javascript fragments in their web pages and get some simple things working. And python seems to appeal to a lot of amateur coders with non computer science backgrounds. At least I know a lot of people with physics, chemistry, bio medical, etc. backgrounds that seem to mainly know python.
Objectively, none of these languages are particularly good at what they do and there are other languages that tend to be preferred by people who code for a living. But these are harder to learn and have higher barrier for entry. You typically need some theoretical background and experience.
I've used all of the above BTW. I learned programming using the manual that came with my Commodore 64 in the eighties (Basic, obviously). I kind of like Python for some things. Javascript is fine for simple things. None of these are my first choice of language but I can work with these languages. They are very approachable languages. Just open an editor and start adapting code.
There's a lot of language snobbery out there where objectively, the advantages aren't that huge or dramatic as people like to believe.
I believe dynamically typed languages are easier to use than old-style statically typed langages (Java, C, etc).
More modern statically typed languages can be quite easy to use (I am thinking of Scala) but none provides an ecosystem as rich as python's.
Plus, recent static languages (Scala, Rust, Haskell or even Elm) are more oriented toward "Software Engineers" or CS academics. Python appeals to a lot of "programmers" who's main job is actually data science, mechanical engineering, biologist...
I think the latter point hits the nail right on the head: the preference for python is probably more of a social issue than a technical one at this particular point in History.
I think when FP languages and paradigms become the norm (you can quote me on this, I predict that it's only a matter of time), preferences will change. People tend to prefer what they know, and it will take some time for culture to change.
I found Scala horribly overwrought compared to either Java or Haskell. It's like it tried to mush together two different type systems and got the worst of both worlds. Languages do not need to be that complex. My next JVM language was Clojure, and I've never looked back. I'd try Kotlin before I'd go back to Scala.
Many reasons, many are of course pretty subjective, but it's been called 'executable pseudocode' for good reason. Out of all the languages I've used, it gets in the way the least, and often by a pretty wide margin.
I agree that working with Python code from an undisciplined/sloppy/inexperienced developer can be a pain (although in those situations I've found code from those same developers in others languages to be a problem too, but maybe worse in Python? I dunno), but in the hands of a solid dev, Python code is so readable and easy to follow, that to me code in other languages often feels much noisier. YMMV of course.
Because you can do a lot with little typing and there are lots of great libraries. Python is probably the best choice when you need to get something done quick or calculate something when performance is not important. It is also not an exotic functional language without loops and filled with linked lists.
Also, in Python integers do not overflow, unlike in Rust, C and other buggy languages.
If you name a language, with high probability Python will be better in something unless it is about performance.
What I don't like: no type hints in many libraries, some libraries are not type-hint friendly, lot of unnecessary dynmamic features (like being able to patch modules in runtime) and absolutely no caring about performance and being able to compile into machine code.
> trying to figure out what some dynamically typed code is doing
What i don't like about python:
The goddamn versioning and packaging problems.
About 5 years ago python was incredible. You could pop out a project quickly because there were very few packages that made breaking changes over their development, so you could often use your standard workflow with the set of packages you we're comfortable with, even if there were new versions available that day.
Now, Nvidia and Google ruined everything. Python is the ML language, so every package likely depends somehow on tensorflow or some GPU acceleration. This is a problem because every damn version of ' insert tf/pytorch/Jax/cuda/etc ml package here' only works with that exact version configuration ONLY ( and honestly, it probably doesn't actually work anyway).
So, if you have an idea of using two packages together that aren't already in a package - be prepared to spend the next month trying in despair to get that to work (and probably ultimately give up).
It's insane to me that the workflow of today necessitates (in python anyway) setting up multiple docker containers to talk to each other, simply because python packages seem to have no problem making breaking changes every freaking week.
I went from loving Python to hating it with a seething rage.
Python package managers are all so bad that the only reasonable solution is Docker containers or vendoring everything (not easy). And even so, I've had untouched Docker builds still die because dependencies mysteriously get yanked or changed.
Scala is a quite "deep" language as it's very powerful (actually it's one of the most powerful languages out there). That can be "overwhelming" to someone just starting out I guess. But regardless some FUD written here and there on the interwebs Scala is not a complicated language. At least if you don't make it so.
Scala was made specifically with the intend to teach people programming. From the ground up.
You don't need to understand monads to understand "Hello World", like in Haskell where printing to the console is an "advanced topic" :-). You can't mess up even the most trivial programs with memory leaks and undefined behavior like in C/C++. You can start out with simple, down to earth code without any friction and slowly build up more understanding for more advanced topics as you go. No need to understand "the monkey holding the jungle" and all that at once.
This is something in which Scala is more or less similar to Python or JavaScript. Actually it's even more amenable to people learning programming from the ground up than the former 'cause it has a very simple "evaluation by substitution" model of operation by default. Everything is an expression, and immutable at first, with makes reasoning about programs as easy as putting numbers into math or physics formulas; something that most people practiced in school for many years already!
To have a smooth start with the language I would recommend having a look at `scala-cli`¹ (for a decent CLI experience, and to be able to bootstrap IDE projects quickly²) and `Metals`³ (as an IDE). (There are also other tooling options like the JetBrains' Scala IDEA plugin, or ENSIME for Emacs fans, but both are less suitable to explore the language in a first step I would say).
Next, no matter what people said you don't need to "fear" SBT⁴ - the default Scala build tool. It's something that looks quite scary to many at first contact but it just works most of the time without any hassle. Especially for simpler projects it's actually brain-dead simple!
Most of the time you just add `libraryDependencies` to your build file by copy'n'pasting a single line form the README of some chosen lib. That's not more complicated than say using Gradle, or pip. Also you almost never have to write your own build tasks as there are SBT plugins for more or less everything one ever would need. Those plugins bring along the desired tasks. Already nicely integrated into your build tool.
(There's also Mill as a build tool which claims that it's "simpler" than SBT. But imho it's actually more fuss for simple projects than SBT where you can just define your dependencies and a Scala runtime version, each with a single line in the `build.sbt` file, and be done. Given that most projects use SBT by default one gets in touch with it anyway quite quickly).
SBT is actually quite powerful (conceptually it's to some extend similar to Google's Bazel); but one can safely ignore all the advanced topics most of the time. At least as long as you don't need to set up a complicated multi-module build with lots of very special requirements yourself. But that's nothing someone just started out would do anyway I guess.
As you can see on its home-page SBT also offers a feature to bootstrap projects form templates with a single command (and there are thousands⁵ of templates). That's something that's really very handy! Especially when playing around with some unknown framework and/or trying out the language.
As people coming form Python are often in the field of data science Scala's notebook support shouldn't go unmentioned. There's a Jupyter compatible Scala kernel out there called Almond⁶. It also integrates the scripting features of the Ammonite REPL⁷. (That REPL is in fact also part of the `scala-cli` tool that was mentioned above). Additionally to that there is a different notebook implementation called Polynote⁸ made especially for Scala (even it's actually a polyglot notebook integrating also Python by default).
But Scala has even a broader surface area: It supports compiling to JavaScript⁹ or to "native code" (through LLVM)¹⁰.
The later option didn't reach version 1.0 by the time of writing but it still may be superior to Graal native images in some cases (at least it had better performance at some point, almost being en par with the JVM; something Graal struggles with still afaik). The JS support on the other hand's side is production ready for a long time by now, and beats something like TypeScript on every axis imho. Especially if one is tired of React's idiosyncrasies something like the Laminar¹¹ JS framework could be a breeze of fresh air!
As seen Scala has a broad area of applications. Even some quite "exotic" like hardware development! For example the Chisel¹² HDL framework is very popular in the RISC-V space. But you have in the HW-dev corner also Scala stuff at the bleeding edge of research like SPATIAL¹³.
Last but not least I think I need to also mention the elephant in the room—which is of course the (pure) functional programming space—where Scala is effectively one of the leading drawcards. With libs / frameworks like the Typelevel¹⁴ ecosystem (with e.g. Cats¹⁵, Cats-Effect¹⁶, Spire¹⁷) or all the great stuff around ZIO¹⁸ ⁽¹⁹⁾ Scala has been even influential in the Haskell world.
Given all that, you can probably see the biggest "issue" with Scala now, too: It's a completely unopinionated language without "guardrails". Whether you like / need to do low-level imperative programming, Lisp-like functional programming, object-oriented design, declarative programming based on (e)DSLs, Haskell-like FP, or anything in between, Scala makes it possible. But it doesn't force anything. You, as the person in charge, are responsible to find the right "style" for your application, needs, and skills. Scala as such does not offer any hand-holding in that regard (frameworks do of course still). That's something that makes the language look "complicated" for some people. Even the language as such isn't complicated, only very flexible.
That said (oh, this got way to long I see), I want to stress that the language is in fact quite simple (given all its power), as it has only a few basic rules. Everything else follows from those as the basic build blocks where chosen with a great deal of tact.
No matter your skill level or previous experiences with other languages you can start writing Scala right away. You can just ignore more or less everything that does not make sense at the given moment. You're not forced to learn and use any particular programming paradigm. Go with what you know already. It works just fine in Scala.
The other great thing about learning Scala is: With time even the most advanced and exciting topics in programming will become understandable (and therefore useful). All that embedded in one coherent language framework.
With a solid Scala understanding almost all features in other languages become obvious. Which, like I said, works also the other way around: You can take your current knowledge and easily apply it to Scala. Just don't listen to any Zealots telling you "you need to do things this or that way". (But don't get me wrong here. Of course purely functional programming is the only valid way to write Scala. Just saying… ;-)).
This is simply not the case. It works sometimes. Maybe.
In many cases (software that is, say, a few years old) you run into packages that aren't available for a certain version. After trying various combinations of pip, maybe even easy_install, and poetry - to come up empty handed, you may realize you need some old library such as qt4, where pypi will certainly not work.
So when poetry or pip doesn't work and pypi doesn't have your specific old version, you may try mamba/conda/ micromamba conda-forge.
But these also probably don't have it either, and your stuck trying to compile old versions in a docker, which still is not guaranteed to work.
There's a reason that package dependency keeps getting rewritten in python. People think they can make it better, but some of the problem is actually just the developer choices by people making the packages. Probably the price paid for having too many people using the language who aren't actual software developers.
I don't know if this is off topic, but I cannot describe the amount that I miss the joy of `python -m venv` in other dynamic languages like ruby. I'm sure there are 100 different tools on github that claim to fix it, but "There should be one-- and preferably only one --obvious way to do it." makes me feel safe in trying out new conflicting libraries in a disposable environment
"Hey, this variable is probably of this type. Now go on, run the code and launch a debugger the same way you would if there was no hint just to make sure it's correct. Oh, and don't forget to check all the possible code paths that lead to this to make sure you've exhausted all possibilities."
Don't get me wrong, type hints are cool but they're just a toy compared to proper static typing.
> Oh, and don't forget to check all the possible code paths that lead to this to make sure you've exhausted all possibilities."
This is literally what mypy does.
There's no difference between a mypy-enabled codebase and a java codebase in terms of static typing (other than that mypy actually supports a generally richer type system).
Like yes if you aren't validating the type hints you aren't getting the value, but if you are, you don't need to run the code! I get most of my python errors at compile time, and you can too. I promise.
Sorry, but MyPy and friends can barely hold a candle to what actual static-typing systems do - Rust, Haskell, OCaml, Typescript, even .Net (C#/F#) offer more safety and guarantees.
Type hints in python are _just that_, they're hints. The whole "typed" python ecosystem relies on packages and the language using them, using them correctly, and using them consistently. In my experience so far, I've seen little widespread use, and nothing from core python, which is a huge hole IMO.
> Sorry, but MyPy and friends can barely hold a candle to what actual static-typing systems do - Rust, Haskell, OCaml, Typescript, even .Net (C#/F#) offer more safety and guarantees.
Note that you don't include the most common static languages there (cpp, java, golang), you include languages known *specifically " for their extremely powerful type systems, one of which , just like mypy, uses gradual typing!
I mean sure, we can include C++, go and java in there if you want? I don’t really know what the point you’re making here is?
> one of which , just like mypy, uses gradual typing!
This is like arguing that because your scooter also has a petrol engine, it’s as fast and efficient as an F1 car. My argument was that MyPy doesn’t compete with the capabilities of modern statically typed languages. TS might also technically be gradually typed, but most devs I know just go straight to fully typed TS, because working in a mix/gradual environment is painful and the community uptake of strict types is far higher.
What features that Javas type system provides does pythons lack?
I can name dozens that python has that Java doesn't. My point is that if you include cpp or java or golang the statement becomes false. Python's type system is actually generally speaking more robust than those (cpp is comparable, but I don't think python's is worse).
So I'm asking, be specific what capabilities does mypy lack that cpp or java or golang provide? Keep in mind, mypy supports literal types, limited dependent types, dynamic-duck typing (protocols), limited contract types, and more. Java doesn't, go doesn't. Cpp supports some of those but it requires arcane incantations.
> There's no difference between a mypy-enabled codebase and a java codebase in terms of static typing (other than that mypy actually supports a generally richer type system).
that must require some advanced usage of mypy then, because my experience was that even the most simple example got the finger from mypy that would have very obviously worked in Java
And look, I'm with you that I wish any codebase with an `__init__.py` anywhere in it (thus, what I think of as a project versus some ad-hoc python scripting whatever) would type-hint the absolute hell out of things, but claiming mypy or any such linting system is as strict as a strongly typed language always boils my blood
I mean I think python's module's are kind of stupid, but even as someone who literally doesn't every use python's module system, my very first thought here was "I bet strict mode is requiring explicit exports in __all__"
And indeed[0] that's the case. It makes perfect sense that the abstract machine of typed python does not allow everything that the abstract machine of untyped python does, and java would error on trying to use a private class or method just the same.
It's imho especially ridiculous to say some linter, that needs "hints" and annotations everywhere, is comparable to a static type system when looking at modern static languages, which have usually very strong type inference.
The point of a sound static type system is that you get a guaranty that your program is type-safe even in case not everything is "hinted properly".
With the linter approach on the other hands side it won't get better than what a dynamic language offers anyway: To be (more or less) sure everything is like intended all you can do is to run your program and pray… You can't upfront rule out for sure all kinds of errors like when using a proper static language.
What is the difference between a "linter" and a "static type system"? Be specific? Pytype, an alternative python "linter" provides type inference. So it's not that. The best I can come up with is toolchain integration, which is exactly what I said.
TL;DR: I would say the difference is the level of confidence in the results of type-checking you'll get.
Of course an external tool could model and check against a proper type system, in theory. So that's indeed not the point.
A proper static type system comes with a formal definition and (math-like) proves of soundness (and progress). This will give you the guaranty that a well typed program is sound.
A "linter" can't give you that guaranty. All it can do is to say "LGTM"; but your program could still crash or bug-out in some other way at runtime. That's quite a big difference imho.
Linters are more like automated tests: They may tell you something is wrong, but they don't give any guaranties that things are actually right. A proper static type system OTOH gives such guaranties.
For example I would consider TypeScript being only a very funky linter—as it does not model a sound type system. My understanding is that mypy and friends are not different. All those linters produce not only way to much false positives, no, they also produce false negatives (meaning that they let unsound programs pass; something that can not happen with a formally verified type system).
As an anecdote, I got bitten by that when trying TS (and it was a very sad experience): When playing around with TS for the first time I did not know that TypeScript is barely a linter. I was really very excited about all those things you can do with proper structural types there! I was coming from Scala 2 which has only very limited support (based on JVM reflection) for this kind of types¹. But I always loved the "objects as hash-maps" concept of JS… So I've written some small REST client for one of our back-end services in TS. Everything nicely typed, no escape hatches used. Someone also told me I have to use "stric mode" which will "catch all type errors". That looked a bit odd, but OK JS was not made to be typed in the first place. Everything looked great than. As someone coming form the purely functional Scala camp (where things work when they compile :-D), and encouraged by all those people praising TS for its "powerful type system", I've put the same amount of confidence as in Scala into the typed code I've written in TS. By this I mean you don't need to double check whether things are correct if the typer says they're correct. It's enough to check the logic of your program. You don't have to try out "manually" (this includes written tests!) every line of code. Up to than everything looked good. So I've pushed things and started to do end-to-end test. That was when I fell out of my dress. My code produced runtime errors which where actually "impossible"! The TS "type-checker" said things are such and such but at runtime they were different (which I found out only after some deep debugging sessions as this was something I did not expect in any way)! I think I've wasted one and a half day debugging this shit only to find out that TS "types" are actually worthless. If I have to try out any and every line of code manual anyway what's the point of TS? I could have written this thing in pure JS (where I have to also check any and every line manually), but it would have been much easier without "wrestling" with that quite useless "type system"… :-(
The point is: A dynamic language remains a dynamic language even if you use some linter! Without true guaranties you can't have much additional confidence in your code without testing just everything—even the parts that were already "tested" by our "type system". Actually it's even worse than without a linter if you don't know about its shortcomings and make the mistake to blindly trust such a tool; like you can do in case of a sound type system (modulo the very seldom cases of compiler bugs).
I hope I could make it now more understandable what I mean by "proper static type system".
> My understanding is that mypy and friends are not different. All those linters produce not only way to much false positives, no, they also produce false negatives (meaning that they let unsound programs pass; something that can not happen with a formally verified type system).
I'm pretty sure the mypy and typescript devs would disagree here. The typescript and mypy abstract machines aren't any less sound than Java or c++.
All four are abstract machines that make fundamentally similar guarantees. Cpp and Java can still encounter classcast exceptions and segfaults.
And, like, you still need tests in functional languages. Specifically, I don't think I've encountered anything like
> My code produced runtime errors which where actually "impossible"! The TS "type-checker" said things are such and such but at runtime they were different
Without either explicitly subverting the type system, or dealing with some kind of untyped deserialization along an api boundary, which you absolutely need to test in any language.
I think I did not object anything you've said. :-)
I only wanted to point out once more that a proper static type system is of more use than some bolted on linter. (And add the point about type inference).
The number of "# type: ignore" comments I've seen in all python codebases that use mypy seems to disagree. mypy is laughably primitive compared to advanced static typing systems: Rust, Haskell, OCaml, etc
> mypy is laughably primitive compared to advanced static typing systems
I don't disagree, but that's not an argument I ever put forth! (and I actually sort of disagree a little bit, mypy has/is getting a bunch of really cool powerful features that put it in the realm of "as cool as" those languages: PEPs 544, 586, 591, 647 (this is a weak form of contract types!), 675 (~constexpr string type), 646 (variadic generic/numeric dependent types!))
Kinda silly that this is optional tooling that came from the community to solve a problem in the original implementation.
Sure, having a community and general inertia, because Python is simply good enough and no alternative has a killer feature, is certainly something you can rely on. It's okay to ask for more, though.
Type hints and static typing are completely orthogonal. It is merely more common for the designers of dynamically typed languages to make the mistake of not allowing type hints pretty much every where and requiring them in a library's API (or at least linting for them being missing in the API) than making the same mistake is for the designers of staically typed languages.
For runtime patching: I needed that twice:
First there was a bug in the stdlib event log logging for windows which crashed when the user was not an admin (2.7 days). We could monkey patch that problem on the fly which was much better than any alternative.
Second I needed to redirect scikitlearn logging to a file. Which did not work because scikit does not use logging but something homegrown. We just overwrite the internal log function and all was good.
Yes it's seldom needed and it's a very sharp sword but it's much better that it's there when you need it. I encountered bugs in .net stdlib which where there was no alternative than to wait for the next release. Very ugly when you need to ship.
Wow, I thought Rust's integers would let you handle integer overflows.. Apparently thats not the case and overflows are only checked in debug builds and then they just panic. Huh.
After programming in Nim (and Python back in the day), I've become used to being able to detect integer overflows as a normal exception. At least for signed ints, since not all CPU support efficient overflow detection on unsigned ints. Its handy to to let uints wrap as sometimes thats what you want. I figured all new languages would do similar things.
Rust has a variety of checked methods -- `checked_add` etc -- that allow for fallible int operations. And there are corresponding `wrapping_` versions that indicate wrapping is the correct behavior (and shouldn't panic in debug builds).
> Also, in Python integers do not overflow, unlike in Rust, C and other buggy languages.
There is no such thing as a buggy language. Software can be buggy (or language implementation), but not the language. And the reason you do not have integer overflow is because integers are implemented as integer objects of arbitrary size, which is great if you're doing something quick and dirty, but could be disastrous for a serious work.
How can you know if you don't have a verified formal description of your language's semantics?
It's very much possible (and imho even quite likely) that a lot of language definitions are self contradicting.
Of course one could argue that such self contradictions aren't "bugs" at all. But such an argumentation would seem very odd, imho, no matter one can actually "define" whatever one likes.
> Signed integer overflow: If arithmetic on an 'int' type (for example) overflows, the result is undefined. One example is that "INT_MAX+1" is not guaranteed to be INT_MIN.
Probably not what the GP meant, but I think this qualifies as a "buggy language (spec)"
Overflowing integers is not a bug. It's literally how an adder circuit operates on fixed precision. Ignoring them by default is worth criticizing but there are plenty of times where silent overflow is correct and desirable behavior.
> but there are plenty of times where silent overflow is correct and desirable behavior
When or where?
I would say it's always a bug in a program—except when (mis)using ints as bit-fields in very special circumstances.
Simply, as more or less every computation in the real world is build around the concept of whole numbers, and not around the idea of overflowing two-complement bit-fields.
The later is so exotic that there is afik almost no proper use for it (the only exception being as a very low level building block to enable computers do actually useful calculations).
-fwrapv forces the compiler to handle signed overflow with 2's complement. UB essentially means the compiler is free to do anything, and with the option it chooses the wrapping behavior.
See https://stackoverflow.com/questions/47232954/what-does-fwrap...
Honestly I guess it's just a difference in taste/demenour.
My background was originally in C# then Java (plus JavaScript on the side, since most of us have to do that), and when I first came to Python I found it to be a lovely language and a breath of fresh air.
I guess I would say to me it feels neat and tidy, thoughful about interface design, and gives me a sense of quality. I get a similar feeling from Clojure (perhaps even more so).
I don't find static typing as valuable as you do, and additionally I find the cost in verbosity and in time wasted solving type-checking puzzles quite painful. Perhaps if I'd spent more time with one of the more sophisticated type systems I'd feel differently (Java and C# are quite limited here), but not convinced enough to try.
But is it the same problem, really? Static typing eliminates a whole class of issues that dynamic typed languages don't. Of course you can still have logical bugs, but it's definitely not the same thing. I don't know how to put it better, but you should try writing haskell sometime and you'll see what I mean.
And static languages introduce a whole class of issues that dynamic languages don't have to deal with. You have to learn a separate metalanguage just for the types that isn't expressive enough to do things that are easy in a dynamic language, and even if it were, you'd still have to write everything twice: once in the real language, and once again in the type language. And then you still have to test, don't pretend you don't. If tests are both necessary and sufficient, then why bother with the type language? The static typing is not worth the cost.
>you'd still have to write everything twice: once in the real language, and once again in the type language
Hmm, no I don't? Actually, 99% percent of the time I'm writing haskell, I can just write it like it's a dynamically typed language and the compiler/lsp will just say "Hey, I can see this is an int/string/list/whatever, want me to annotate that for you"?
Hindley–Milner type systems are way way too powerful and I can count on my fingers how many times the compiler was confused when inferring types and I had to manually annotate it.
And yes, of course I have to test the logic of my code. What I don't have to test is that my code behaves when given wrong-type values and that all the code calls functions with values of the correct type, because the compiler is doing that for me.
I haven't worked with Haskell enough to fully object to this, so any complaints I could come up with would be second hand, so I'll abstain. I don't feel fluent in Haskell yet, but my impressions so far were mostly not negative.
I'm mostly complaining about static typing in the style of Mypy/Pyright and Java (and half of Scala, the other half is like Haskell). You know, the static typing one is likely to encounter in industry. But even Hindley-Milner isn't as expressive as fully dependent types like Agda or Idris. If you're going to use static typing at all, why not go all the way?
Scala is going to get "full" dependent types at some point. It's work in progress for a long time already (and it's actually quite close by now).
Besides that your argumentation makes no sens anyway whatsoever:
Fully dependent languages are undecidable by type inference alone. So you're forced "to write everything twice" especially in such a powerful language. But that's actually the whole point of it! Like you duplicate large chunks of your code when writing tests to verify your code in a dynamic language that exact same "duplication" in code when using proper static types is what actually helps to avoid casual errors. But the difference is of course that in the later case the machine can verify that both parts match up (which it can't in case of usual tests!).
On a side note: Why have you such strong opinions about typed languages if you didn't had much used a proper one at all as you say? Of course a type system like that of Java or C/C++ is a huge PITA, but that says nothing at all about static typing in general. Actually quite the contrary as Java's type system is especially painful (and therefore you need to cast your way through the whole time, which is not what static types are for in the first place). But you make general statements about static typing here the whole time. That doesn't seem justified imho.
From the quotation marks, I surmise that you're wondering what a non-full dependent type system could possibly mean. I added that qualifier because Python, in fact, had one, last I checked, with its `Literal` type (https://peps.python.org/pep-0586/#rejected-or-out-of-scope-i...), which is "a very simplified dependent type system", according to the PEP, but "True dependent types" are out of scope, at least for now.
> From the quotation marks, I surmise that you're wondering what a non-full dependent type system could possibly mean.
That wasn't the point. Scala has a few variants of dependent types but that features are constantly diminished as not being "full" (or sometimes even "real") dependent types. I don't buy that as dependent types aren't defined as being like dependent types in say CoC (the Calculus of Constructions). For example Singleton types match also the definition of dependent types when derived from literals; even such types are still weaker than the usual "full" dependent types à la CoC.
Regarding Python: Python has only one type, the "almighty-python-runtime-type", as it's a "dynamic" (unityped) language. Therefore it does not make much sense to talk about "types" in Python at all…
I do think that's helpful compared to the strict alternative, but much of the up-front cost is still there. You still write most things twice, and the static type checker still slows you down when prototyping. You'd still be tempted to write bad code to work around the insufficiently-expressive type language, rather than write it in the most natural way, and only give up when it's too hard.
You can also approach this from the other direction: why not start with a dynamic language for the rapid prototyping and gradually introduce typing as the code stabilizes? Python and Typescript do this.
> You'd still be tempted to write bad code to work around the insufficiently-expressive type language, rather than write it in the most natural way. […]
Could you provide any evidence for the claims that one needs "workarounds" and can't write code "in the most natural" way in a statically typed language?
> You can also approach this from the other direction: why not start with a dynamic language for the rapid prototyping and gradually introduce typing as the code stabilizes? Python and Typescript do this.
Which obviously does not work, which is the whole point of this submission and discussion thread…
These aren't just some third-party tools bolted on. The type annotation syntax is built into the language[5] and standard library[6][7].
I personally find static typing to be more trouble than it's worth most of the time. Industry typing metalanguages are not expressive enough to deal with even fairly basic real-world programs and force you to write bad code to work around the type checker's stupidity. And, of course, you still have to write tests. Maybe someday they'll catch up to Idris. Python's static typing is no better, but at least it allows you to turn it off when it's not worth it.
> Industry typing metalanguages are not expressive enough to deal with even fairly basic real-world programs and force you to write bad code to work around the type checker's stupidity.
I honestly don't see that. Could you provide some examples?