I think the big big result from this study is: "Python: half the size !".
A dynamic language like Python is better here, 2x better. I assume similar results would apply to other dynamic languages like JavaScript, Lisp, Smalltalk, Groovy etc.
This does not say that static typing should not be used but I think it shows unequivocally that there is a considerable extra development cost associated with static typing.
You might say that surely that additional cost would be compensated in reducing the cost of maintenance later. Maybe but I'm not sure.
Any development effort of significant size (like writing a compiler here) is a combination of writing new code and adapting and modifying code already written. "Maintenance" is part of development.
This is quite a quantitative study which gives credence to the claims of advocates of dynamic languages.
I enjoy hacking in dynamic languages as much as the next programmer. But, the big take-away is that "the initial implementation was done in 1/2 the code" not that the resulting code was more extensible or maintainable (by other programmers!).
> You might say that surely that additional cost would be compensated in reducing the cost of maintenance later. Maybe but I'm not sure.
I am sure. 100%. From many years of experience.
Yes, static types come at an initial cost at initial development time. But they pay that time back in spades. This is exponentially true the larger the code base is (more to keep in one's head), the longer the project lives, and the more people are on it.
Having worked on very large C/C++, Scala and Python projects, when it comes to add a major feature or perform a serious refactor, I always want the static typing and the compiler to inform me when I've missed something. Far too many times has code been checked into a large Python code base that breaks something (completely unbeknownst to the programmer), because there's a code path that's rarely executed or arguments to a function flipped, etc.
That all said. There are major benefits to being able to prototype very quickly in a dynamically typed language, too.
Lately I've been doing a lot of greenfield development in Python, developing libs for in house use at my employer. We're using 3.7 currently, and I've fully embraced type hints. With proper type hints and use of pylint, you get the static checking that you'd otherwise miss. Bonus, if you're using an IDE like PyCharm, VS code or visual studio, you usually get the linting for free either as you type or on save.
There are not too many dynamically typed languages that truly allow you not to miss static typing. One of them is Clojure. I can't explain exactly how, but somehow I think Clojure is fine without them. I don't think I can say the same thing about JS, Lua, Python or Ruby.
I spent a couple of years working in a Clojure shop with people who actually like Clojure, and the experience for me was not so practically different than if everything had been written in Ruby (and indeed, half the codebase was a legacy RoR system).
You either have a type-checker and compiler, or you don't.
They used Spec. Indeed wise use of a tool like this does make a difference, but then the onus is on the developer to be disciplined enough to apply it appropriately. Humans do not by default have this discipline.
When thinking about extending and refactoring, you also need to keep in mind that static types add a whole layer which is pretty hard to change. There are advantages and disadvantages, having at static types isn't such a silver bullet.
Hm... well that might be true for extending, I'm not sure you've fully thought through that comment.
Duck types are great for writing new code, but they're very troublesome for refactoring; automated tools have a much harder time automating that process.
Refactoring data structures and implementations in static type systems is bother considerably easier
to implement than in dynamic languages, and resultingly, more robust.
Certainly the refactoring tooling these days is pretty sophisticated with type inference, but... well, I've refactoring large python and javascript code bases, and my experience has been that absolutely a static type system makes that process easier, even if you have a comprehensive test suite.
I think it's worth acknowledging that there is a place for static type systems; certainly, it's not a silver bullet, and it results in (usually) a higher lines-of-code count, which is significant; but its naive and wrong to suggest that it has no value at all.
Specifically, for refactoring, it has a lot of value.
So do all the implicit assumptions in duck-typed code, but following them relies fully on the programmer's own caution.
You can of course add explicit checks and tests, eventually paying the same amount (or more) in LOC as the typed implementation's initial cost, but then you're also tasked to keep those up to date, without the compiler's aid.
The solutions to these problems in "extending and refactoring" are the same in dynamically and statically typed languages because in both you have to make sure functions/methods get the correctly typed input and return some expected type as output. In both worlds you will either refactor everything, abstract the problem away with interfaces or union types or you'll do type conversions at the boundaries of the code you touch.
That layer is there anyway, except for your interpreter of a dynamic language only knows about problems at run-time, while the compiler that checks a static type system will tell you at compile-time.
I agree it's definitely an interesting result and a point in favour of dynamic languages.
A caveat is that I'm pretty sure my friend intentionally sacrificed code quality to do it, I don't think you'd find that project as readable and understandable as the others. Another caveat is that you have to be okay with your codebase being extremely magical and metaprogramming-heavy, which many industrial users of dynamic languages avoid.
As I mention, I'm personally into statically typed languages mostly for the performance and correctness benefits. I think it's plausible that on larger projects with teams the correctness benefits save enough debugging time that overall implementation time is lower, but I'm less confident in this now than I was before this comparison.
I've read my share of cryptic JavaScript written by others and in that sense I agree that in multi-person long-term projects static typing no doubt will have its advantages.
My hunch is however that what is often overlooked in development with statically typed languages is that it takes considerable time and effort to come up with the right set of types. Many examples are written showing how types almost magically make programs easier to understand. But when you read such an example what is not stated is how much effort it took to come up with just those types.
One way of thinking about it is that type-definitions are really a "second program" you must write. They check upon the primary program and validate it. But that means you must write that second program as well. It's like building an unsinkable ship with two hulls one inside the other. The quality will be great but it does cost more.
No matter what, you need a rigorous schema for your data. If you write a complex JS/Python program without doing the equivalent of "come up with the right set of types" then you will have a bad time. I'm sure in the OP here the skilled Python programmer did think carefully about the shapes of her data, she just didn't write it down.
To be sure, having to write down those data structure invariants in a rigorous way that fits into the type system of your programming language has a cost. But the hard part really is coming up with the invariants, and it's dangerous to think that dynamic languages obviate the need for that.
It's also hard to massage your invariants into a form that a type checker will accept, since you're now restricted to weird, (usually) non-Turing-complete language.
A good example of this is matrix operations - there are plenty of invariants and contracts to check (e.g. multiplication must be between m x n and n x p matrices), but I don't believe there's yet a particularly convincing Haskell matrix library, in part because the range of relevant mathematical invariants don't cleanly fit into Haskell's type system.
For those cases, checking the invariants at runtime is your escape hatch to utilize the full expressive power of the language.
This particular example can be encoded into the Haskell type system though. For example, there's a tensor library where all operations are (according to the description) checked for the correct dimensions by the type system. It seems to require a lot of type-level magic though, and that may disqualify it for "cleanly".
> But the hard part really is coming up with the invariants,
Surely. But if you have to write them down it becomes hard to change them because then you will have to rewrite them, and you may need to do that many times if your initial invariants are not the final correct ones.
The initial ones are likely not to be the final correct ones because as you say coming up with the invariants is ... the hard part.
What I'm trying to think about is that in a language that requires you to write the types down they have to be always written down correctly. So if you have to change the types you use or something about them you may have a lot of work to do because not only do you have to rewrite the types you will also have to rewrite all code that uses those types.
That does allow you to catch many errors but it can also mean a lot of extra work. The limitation is that types and executable code must always agree.
Whereas in a dynamic language you might have some parts of your program that would not even compile as such, if you used a compiler, but you don't care because you are currently focusing on another part of your program.
You want to test it fast to get fast feedback without having to make sure all parts of your program comply with the current version of your types.
A metaphor here could be something like trying to furnish a house trying out different color curtains in one room. In a statically typed language you could not see how they look and feel until all rooms have curtains of the same new color, until they all follow the same type type-constraints.
"that it takes considerable time and effort to come up with the right set of types. "
I've written once here before, this is one of the 'accidental advantages' of TypeScript: you set the compiler 'loose' when you're hacking away, writing quickly, and then 'make it more strict' as you start to consolidate your classes.
I almost don't bother to type something until I have to. Once I see it sitting there for a while, and I know it's not going to change much ... I make it a type.
It's an oddly liberating thing that I don't think was ever part of the objectives of the language, moreover, I can't think of any similar situation in other (at least mainstream) languages.
You can do that in Haskell also. Just turn on the -fdefer-type-errors GHC option and leave out most of the type signatures. Any expression with a type error will be reported when/if the expression is evaluated at runtime. You'll probably still need a few type hints, to avoid ambiguity, but otherwise it's not that different from programming in a dynamically-typed language.
The takeaway for me was: "The next comparison was my friend who did a compiler on her own in Python and used less than half the code we did because of the power of metaprogramming and dynamic types."
So it's the output of ONE Python programmer vs teams of other languages programmers?
It is well-known that teams cause overhead. Think of the Mythical Man-Moth.
But in the end the other teams had 3 people that were able to maintain and develop their project further, if needed. The single-person team had only one such person.
"Smaller code" does not mean easier to understand. It just means less characters to read. Maybe those characters are heavily loaded with meaning, as in the case with meta programing. You might need twice as much time to _understand_ the Python Code vs. the Rust code. The Rust code might be easier to extend, etc. So this is all comes to trade-offs at the end.
All this being said, I'm still a huge Python enthusiast.
It was also stated, though, that the reason she worked on her own was that she was a very good programmer; presumably, better than a lot of the people who worked in groups. And, as the sibling mentions, teams introduce overhead.
It's pretty clear that (like in every programming teams) the size of the output (in LoC) was linear with the number of programmers, which is something our profession should be worried about.
In my personal notion of code quality, being correct counts for a lot, and this programmer aced the tests, while apparently delivering more within the deadline that any of the multi-person teams.
While writing the previous paragraph, I wrote 'programmer' where I had previously put 'implementation', because I would guess that this study's outcome is better explained by human factors than language differences.
I share the attitudes you state in your last paragraph, but I would add that we should be skeptical of concepts of quality that seem plausible, but which lack empirical evidence for their effectiveness.
Tangentially - do you know how fast her compiler was compared to the compiled-language implementations? I have a vague sense that for many applications, interpreted languages are totally fine and reaching for a compiled language is premature optimization, but I'm curious how it actually holds up for a compiler, which is more computationally heavy than your average web app or CLI.
No I don't know how fast her compiler was. This also isn't a good setup for comparing language performance, since all the test programs were small and there was no incentive to make your compiler fast. Many groups probably used O(n^2) algorithms in their compiler and differences in algorithm choice would probably add too much noise to get a good performance signal.
That aside, speaking off the cuff totally separately from my post, I'm extremely dissatisfied with the performance of current compilers. The fastest compilers written by performance-oriented programmers can be way faster than ones you generally encounter. See luajit and Jonathan Blow's 50k+ loc/second compilers and the kind of things they do for performance. One example of a compiler task it's really difficult to do quickly in a language like Python/Ruby is non-regex parsing, I've written recursive descent parsers in Ruby and compiled languages and the Ruby ones were 1-2 orders of magnitude slower, non-JIT dynamic languages are really bad at tight loops like that.
> I'm extremely dissatisfied with the performance of current compilers. The fastest compilers written by performance-oriented programmers can be way faster than ones you generally encounter. See luajit and Jonathan Blow's 50k+ loc/second compilers and the kind of things they do for performance.
Lua and Jai are lot less complex than say C++: sure, LLVM isn't necessarily built to be the fastest compiler in existence, but I don't think it's fair to compare it to compilers for much simpler languages and bemoan its relative slowness.
I wrote the python compiler. It's very slow. With the optimizations it's probably something on the order of O(n^3) or O(n^4). One of the test cases took seconds to compile. I made no effort to optimize the compiler speed.
Yep, the takeaway for me was that the Python project required far less code, but we're not sure how fast it ran. Further below, the author states the inputs were so small it didn't matter. What if it did? Would the Python solution still be viable?
0.5x with metaprogramming vs 0.7x without in Scala, isn't "far less". This also matches my experience - Scala is pretty on par with Python in terms of code length. It gets a tiny hit from being statically typed, but then makes up for it by having richer stdlib and powerful type-level abstractions.
Less code doesn't imply lower quality code. A more expressive language + less boilerplate allow you to write high quality, readable code with fewer lines.
My experience is that you always use a type system. If the language doesn't include one, you'll end up making a bunch of extra tests and control code that in practice is constraining your data to resemble a type. It's a worse system: more verbose, harder to refactor and it's way easier to miss corner cases.
It's either that or assuming that your data will contain exactly what you want to, ignoring every possibility outside the happy path, which is a recipe for disaster.
That said, I have to say that modern JS produces the cleanest looking and less verbose code I've ever worked with so far. I wish there was a way to work with types without adding verbosity and harming readability.
Seems like a bit of a bold claim when the author themselves directly contradicts that in their own conclusion:
> I think my overall takeaway is that design decisions make a much larger difference than the language
After all, Scala was 0.7x the size and is one of the most strongly statically typed languages. So you could almost invert your conclusion and say the big result is
"Python only saved 20% code lines over fully statically typed language"
No I would not reach quite that conclusion I would say
"Python saved 20% code lines over Scala!".
The results say something about the greatness of Scala, not of statically typed languages in general. The other ones did not do quite as good as Scala.
From what little Scala I've read it looks very terse indeed. That can be a benefit but at the same time makes it harder to understand code written in it, in my opinion.
I think most people would consider 20% basically within the margin of error induced by stylistic and other non-meaningful differences.
For example, it's not completely clear from the post but it seems like the 0.5x figure is from wc -l, which means Python wins a line every time there is a conditional or loop just because it doesn't need a closing brace. That alone might eat up a lot of the 20%, but you would be hard pressed to say that is a meaningful difference.
My surprise from this study was simply that dynamic languages are clearly not much worse than the best-of-breed statically typed languages. Maybe 20% is within the margin or error, but you definitely can't take that as any evidence that Scala is "better" than Python.
The reason I think this is "big big news" is I thought the general consensus had already been reached in academia if not the programming community that "statically typed functional languages are much better". There's little or no evidence of that in the results of this study.
It is actually nearly a 30% reduction with respect to the Scala line count.
More to the point: while the numbers nominally show differences in languages, everyone with programming experience recognizes that unmeasured and uncontrolled human factors probably had a big part in the outcome.
I know. It's strange to say that. But Scala's library is very very rich.
Another example is that Scala offers a lot of ways to process a list like foldLeft, foldRight, unzip, headOption, lastOption, flatMap, groupBy, and many methods around Map, Set, and etc. Python probably doesn't offer many of these methods.
Of course, this comes with the cost of higher learning curve.
Yes, itertools is great. But its stblib is still much lighter than Scala's and Ruby's.
Actually, that seems to be the direction/principle of Python, where it is less inclined to add a helper function.
"It has been discussed ad nauseam on comp.lang.python. People seem to enjoy writing their own versions of flatten more than finding legitimate use cases that don't already have trivial solutions." from https://softwareengineering.stackexchange.com/questions/2542...
Not that this is better or worse. It's just that, on the brevity aspect, Python code is gonna be longer.
While what you say is true, my small example shows that the code is longer in Python for solving the same problem, i.e. `my_list[0] if my_list else None` is longer than `my_list.headOption` or `my_list.first`.
And we are talking about the brevity of a language here.
I'm not sure what your example is illustrating. The SO question asks for the idiomatic one-liner to get the first non-null element of a list. The accepted answer does that.
Maybe I should have given the comparable example in Ruby, which is `array.first`. Even Scala offers `array.headOption`. Both are more succinct than Python's.
The degree of richness and/or the height of abstraction seem lower in Python. (Not that this is a bad thing. It depends on people's taste, of course.)
Python is indeed very frustrating that way. So many of its limitations and flaws are justified on the basis of clarity and then there are this array of very simple things that can only be expressed in unclear ways which every other language offers a solution for.
A comparable example from JavaScript, there is no library-method for getting the last element of an array, so it gets clumsy: myArray [myArray.length - 1]. A better standard library would provide a method for that: myArray.last(). Or maybe myArray[-1].
Sounds like you just don't like Python, but you don't have great reasons for not liking it. The standard library is fantastic in Python. Find better reasons to back up your unfounded dislike.
Saying Ruby/Scala have richer standard lib than Python isn't really a stab at Python.
Like dragonwriter says, it shows that "Python expresses the concept fairly compactly in the core language without even resorting to stdlib".
It depends on your taste whether you like richer stdlib, and I do. But some don't.
We are talking about how short the code can be in this post, and `my_list[0] if my_list else None` (Python) is longer than `my_list.headOption` (Scala) or `my_list.first` (Ruby).
I'd appreciate more if you elaborate why my example isn't a good illustration on the brevity aspect.
I think there are just a lot of people here getting touchy and defensive because other people aren't automatically leaping to false conclusions from this article's data, preferring to imagine that the article validates their personal choice of favourite programming language rather than engage in proper speculation about the quality of the conclusions made within the article.
I would agree that the sample size was small so programmer competency may be a big factor.
The interesting takeaway from this study I think is that it does not show that statically typed (even "pure") functional languages are not obviously better than plain old Python.
The interesting thing is not what this study proves, but what it does not prove.
> The interesting takeaway from this study I think is that it does not show that statically typed (even "pure") functional languages are not obviously better than plain old Python.
Could you elaborate more on this statement? Not a native english speaker here, so I don't quite understand the sentence. Thank you.
> I think the big big result from this study is: "Python: half the size !".
> A dynamic language like Python is better here, 2x better.
I was surprised by how small the LOC benefit was from using the dynamic languages. As someone who typically reaches for Python, I'd use a statically typed language (Go or Java, most likely) much more often if I expected only twice as many lines of code. In practice I feel the same project takes 3-10 times as many LOC and that pushes it to where it is more difficult to maintain and understand.
I have the same experience with Java. But I would have expected languages that have type-inference like Haskell, Scala and OCaml to do much better. But maybe their advanced features in fact make programs written in them harder to understand, which slows down development. Don't know.
Note my half the size estimate was after estimating the amount of code dedicated to extra features. The raw line count is only 0.7x the size of our non-test line count. Although my estimate of how many lines it would have taken without the extra features has very wide error bars.
"This is quite a quantitative study which gives credence to the claims of advocates of dynamic languages"
You have to be careful with that.
In a dynamic language your test system is your compiler. It is essentially a domain specific compiler. With a statically typed language, half your tests are already written, and you just have to activate them with a type signature.
For my money the trade off is down to whether forcing structures into a type system and getting the 'free advanced tests' is more advantageous than constructing a domain specific test harness 'compiler' and getting the flexibility of duck typing.
And you only learn that when you run up against the limits of the type system and have to start complicating structures to work around it.
In terms of a rough metric I'd suggest you have to include the test code in both cases to get a fair comparison.
> I think the big big result from this study is: "Python: half the size !".
> This does not say that static typing should not be used but I think it shows unequivocally that there is a considerable extra development cost associated with static typing.
Are you saying that the bottleneck in software development is typing in text on a keyboard?
If so, I thoroughly disagree. In my experience, inputting text is a small factor in development cost, with the main cost being research (figuring out what to type in), and debugging (figuring out why something you typed in doesn’t work as you thought it would).
> I think the big big result from this study is: "Python: half the size!".
I would agree, if and only if I thought a representative sample of Python programmers would all produce something of a similar size and just as correct, but I suspect, in this case, it's the result of one especially talented person.
Raw talent and prior experience no doubt had something to do with it, but there's also the fact that the Python programmer was working alone and not particularly concerned about code quality or maintainability.
I agree (with the caveat that, as I wrote in another comment, I think being correct is an important aspect of quality), but this observation also argues against claims that attribute the outcome to the qualities of Python.
Indeed, design choices accounted for the biggest difference by far.
I like that the title frames it as a language shootout to pull people in to see if their favorite language wins (and I'm partial to Python having rewritten tens of thousands of lines of Java into numpy). Still, it would be foolish for people to come away from this brilliant analysis by ignoring the more important conclusion.
This comparison doesn’t tell absolutely nothing about the strength and weaknesses of dynamic types languages vs static types languages in real world projects.
Obviously dynamic languages are better for toy projects.
If for you 4-5k loc is a project of “significant size” then we must have pretty different points of view.
How do we know the big result is "Python!" rather than "work alone!" or "women code better!" ? Because that was 3 features of that one sample. It's hard to make any conclusions from that article.
I also was surprised at the magnitude of the difference and am not sure the extra lines are worth it given the strong association between number of lines and number of defects.
Having said that, the largest point I took away was that the difference between languages was smaller than the difference between programmers and approaches.
I don't think the reason why these languages are terser is because they are dynamic. Something that always frustrates me to no end is how many older statically types languages barely have a literal syntax.
Literal syntax and stdlib size and APIs. The latter is especially important on iterator and container interfaces. They must fit together well and be composable.
This can be exemplified using Crystal, which is close to Ruby in both terseness and APIs, but statically typed (and with static dispatch).
The work was done under a deadline, and some teams did not complete all of the assignment in the allotted time. The Python programmer not only completed all the required and optional features, passing 100% of all tests (both the revealed and secret ones), but "also implemented more extra features (for fun) than any other team, including an SSA intermediate representation with register allocation and other optimizations."
A dynamic language like Python is better here, 2x better. I assume similar results would apply to other dynamic languages like JavaScript, Lisp, Smalltalk, Groovy etc.
This does not say that static typing should not be used but I think it shows unequivocally that there is a considerable extra development cost associated with static typing.
You might say that surely that additional cost would be compensated in reducing the cost of maintenance later. Maybe but I'm not sure.
Any development effort of significant size (like writing a compiler here) is a combination of writing new code and adapting and modifying code already written. "Maintenance" is part of development.
This is quite a quantitative study which gives credence to the claims of advocates of dynamic languages.