Ask HN: When has switching the language/framework made an important difference?
I'm curious about real world examples where a change (preferably measurement/profiling driven) has lead to a significant positive outcome in performance, code quality/maintainability, etc.
Did changing from Python to Go make it so that you could avoid horizontal scaling for the size of your app, reducing operational complexity?
Did switching from Dart to Rails speed up development because of the wide range of libraries available, speeding up time to market?
While most bottlenecks exist outside of languages/frameworks, I find it interesting when the language/framework actually made a difference.
An example I'll use is switching an internal library from C# to F#: The module as designed mutated 3 large classes through a pipeline of operations to determine what further work was needed. I incrementally rewrote this module in type-driven F# with 63 types to model the data transformations and ensure that the correct outcome desired was compiler verified. In the process 3 bugs were fixed and 12 additional bugs were discovered that while edge cases, had a couple of old tickets with "unable to reproduce" as the last comment in the ticketing system. This could have been done in C# and because I did it in F# it is most likely slightly more difficult for the other team members to jump into. It probably also uses more memory to represent the types than the C# version. In this case however, the trade offs were worth it and I've been told the module has barely needed to be touched since.
151 comments
[ 9.1 ms ] story [ 389 ms ] threadIn this case the original code had a class like:
The code would take a single DeliveryItem and each time something used it, it would update the properties on it.While not ideal, this worked fine if pipeline was an ordered sequence. The pipeline had grown into a complex graph, with many different possible states, and new properties were incrementally added to shoehorn the new states and transfers between states into the single object.
The class grew large and it became difficult to determine from logs what path of the pipeline that an item had taken. It could have been scanned, weight analyzed, compaction determined, etc, etc. And these didn't always happen in the same order because of the business process.
Instead I created types like `NewDeliveryItem`, `PreProcessedDeliveryItem`, `ThirdPartyVerifiedDeliveryItem`, etc.
It allowed for explicit modeling of the pipeline, since functions could take a `PreProcessedDeliveryItem` instead of a `DeliveryItem` and would know that they wouldn't need to send it for processing.
The example has been translated a bit and isn't the best explanation, but gives a small amount of detail.
You can check out https://fsharpforfunandprofit.com/series/designing-with-type... and https://fsharpforfunandprofit.com/ddd/ to get more information on how this can decomplect seemingly simple applications.
I wanted to build a database in a dynamic language. While others have succeed to do so by layering their DB on top RDBMS (like EdgeDB or Datomic) I went lower level and built a datomic like DB with GNU Guile Scheme using wiredtiger (now mongodb storage engine). The reason for that is that Guile doesn't have a Global Interpreter Lock (GIL). Using the same design in Python would simply not be possible. I did not benchmark, but I don't think it's possible for a single thread DB to be faster than multithread DB. In this chance changing language made the project possible.
Another example: same language, new framework: In a Python web app, we needed to have websockets. But at that time Django had no real websocket support. But there is future proof framework that does: aiohttp! Also one might argue that you can use old django with websocket using another process. But it leads to a more complicated architecture. We want to keep monolith the app as long as possible/sane.
I wish it were an easier thing to do, but I think as far as maintainability and codebase size scalability go, it's hard to quantify properly without metrics that are usually only tracked in enterprises.
I'm trying to think about how to make this more clear in the original post.
But in general, moving from PHP, Python and JS (where I started out years ago) to languages like Java, Flow-typed JS, and even Scala at work have made me realize how much dynamically-typed large projects loose in maintainability vis-a-vis statically-typed projects. Yes, excellent test coverage can help mitigate this, but static types go so much farther when used effectively.
Introducing types to the server side language has never done anything for me other than making sure that the types are in sync with those of the database while creating a lot of tedious overhead converting data that the application itself might not ever need to process to a particular type.
The Elixir gradual typing approach has always seemed like the ideal balance to me.
Think more in terms of the code state than the conceptual/business state, because when the code state is readable and understandable and low-overhead, it leaves you all the brain space to think about the problemset. No matter what, working on establishing patterns, share them with other people working on the same codebase, and make them better over time - build only enough tests so that you can cleanly refactor whole files, while not bogging everything down in the details. If you are ever afraid of changing a file (except at top level tooling), even in production, you're doing something wrong, because the potential footprint of your changes should be as low as possible.
Unless you have a problem that fits a specific technology really well, my experience is that your time to market will be minimized by using the tools you know best.
When I pulled the trigger on it I was terrified that I was screwing us over by not using Angular (which was the cool tech at the time) or some other more javascript-oriented solution. Thankfully it has worked out well, and my co-founders don't hate me any more than they already did before hand. (And maybe even a bit less.)
Meaning, if I had access to creator of framework A on my team, and was choosing between framework A and B, and objectively liked B better than A (but not by too much), I would still choose A.
This has been a big win in terms of the readability of the code, which has in turn made me more aggressive about adding features.
http://www.adacore.com/knowledge/technical-papers/sparkskein...
byte_count := (n+7) / 8;
When n is near max size, it overflows to a small number, then (small number) / 8 = 0
the fix is to restrict n to [0,MAX-7)
In C:
Much better if you can let the n+7 expand to a wider integer type. Or better yet, if you can ensure that it won't overflow in the first place -- which is what they did by restricting the type.
(with eax being the unsigned 32-bit input word)
(edit: replaced a movzx with an xor)But clever compiler still give me the willies, with their less-than-literal interpretations of the meaning of what I wrote.
I prefer code that is efficient good (whether that means fast, or something else appropriate to the circumstance) when taken literally, and then trust trust the compiler to smooth over my naivity about what efficiency really means.
The script optimized the placement of samplers in a building, in order to maximize the probability of detecting airborne pollutants, or to minimize the expected time required to detect. The rewrite cut the runtime down from 2-3 days to sub-hour.
Some of the speedup was intrinsic to the interpreted/compiled divide. However most of the speedup came from the greater control Fortran gave over how data got mapped in memory. This made it easier for the code to be explicit about memory re-use, which was a big help when we were iterating over millions of networks.
Re-using memory was helpful in two ways, I think. First, it avoided wanton creation and destruction of objects. Second, and more importantly, it allowed bootstrapping the work already invested in evaluating network `N` when it came time to evaluate a nearly-identical network `N+1`. Of course, I could have made the same algorithms work in R, but languages like C or Fortran, which put you more in the driver's seat, make it a little easier to think through the machine-level consequences of coding decisions.
That experience actually taught me something interesting about user expectations. When the Fortran version was done, my users were so accustomed to waiting a few days to get their results, that they didn't run their old problems faster. Instead, they greatly expanded the size of the problems they were willing to tackle (the size of the building, the number of uncertain parameters, and the number of samplers to place).
Thank you, now I have a second example the nest time someone claims "nobody used fortran anymore".
I am not sure if this performance advantage is still the case as the wikipedia page on pointer aliasing notes that C99 added a "restrict" keyword so that the C programmer can tell the C compiler where similar strong assumptions can hold.
You could also get much faster if most of your work is matrix multiplication if you make use of libraries like ViennaCL and a modern C++ compiler.
https://docs.scipy.org/doc/numpy-1.10.1/user/install.html
"To build any extension modules for Python, you’ll need a C compiler. Various NumPy modules use FORTRAN 77 libraries, so you’ll also need a FORTRAN 77 compiler installed."
That aside, you haven't explained why you think the languages you listed are better suited for scientists than Fortran. What are your issues with modern Fortran?
Don't know if C++ can ever be as fast as Fortran because all fortran compilers are optimized for the architecture on which they run. (Best fortran compiler for intel cpus is by intel). As for ViennaCL I don't know much about GPU programming and its performance. Never done it. (My only exp is with Cython and Scipy stuff)
And, BTW, I have seen a case where PGI vastly outperformed GCC on the same code. So compilers matter.
Julia is regarded as the modern replacement for Fortran, but it isn't quite there yet.
The major problem with Fortran is that there is (sadly) nearly no documentation on modern idioms, and occasionally you have to dive in legacy code, which can be... scary, to say the least. Otherwise, it was upgraded really well, without losing performance.
There is some C++ code that is very bad out there but most C++ code maintained by normal people is passable. When I use it I use it as C with references.
> And all are worse performance-wise.
You are incorrect just by the facts of the situation [0][1][2]. This lie has stuck around so lazy people can pat themselves on the back for being lazy. "Yay we did it! We found the best language every! We don't need to learn anything new or update anything!".
The truth is a modern C/C++ compiler like GCC or clang/LLVM can generate far more efficient code than Intel's FORTAN compiler. Not only that but you have access to more performance and scalability libraries when you're not using the programming language equivalent of Latin. You're never going to get out of the MPI ditch that scientists have dug for themselves in a museum piece like FORTRAN. There will be no good abstractions, no well planned libraries, no in language support for newer hardware.
FORTRAN is going to be stuck in the 70s and 90s.
Languages like C++, Python's libraries, Julia, and other languages (like D/Go/etc) are going to evolve as industry needs them to. As we start being able to use less and less of the our computers abilities compilers, optimizers, and libraries are going to allow us to easily pick up the slack. You can already see this things like ViennaCL. GPUs, FPGAs and other tools are coming. MPI and FORTRAN can't be the only tool in your toolbox and if you're lying to yourself and burying your head in the sand in an attempt to pretend you can then good luck to you. You're wasting your budget, the taxpayer's money, and everyone's time on the supercomputer's queue because you don't want to try something new.
But it's fine. Who cares really. FORTRAN is the only language a scientist will ever need. I mean it is the fastest (even if it isn't) and it has the best compilers (even if they aren't the best and we waste thousands and thousands of dollars of taxpayer money to get them) and we know it and that's all that matters. Who cares about broader impact, portability, and future proofing.
[0] - http://benchmarksgame.alioth.debian.org/u64q/fortran.html
[1] - https://julialang.org/benchmarks/
[2] - http://benchmarksgame.alioth.debian.org/u64q/compare.php?lan...
You say you can get matrix libraries for C++. And you're right, you can. But with Fortran, you can get (for example) functions that will solve Hermitian matrices, which run faster than solving regular matrices. Or special routines for sparse matrices, which run faster and use less memory. Or...
And you could write every one of those in C++. But the Fortran ones have 30 years of debugging and optimization on them. They're solid. You can't get that by writing your equivalent in C++.
Most of this and related applications engage in data shuffling rather than number crunching, but FORTRAN used to be one way of creating code that ran at nearly assembly language speed and size in (historically) memory constrained environments.
That said, the application (and I) are nearing retirement.
similarly, the backend of the original "random forest" machine learning algorithm for R was written in fortran, and then wrapped as an R library, but as an R user you could largely pretend that fortran didnt exist. R was a pitifully slow language so this was a good move.
One of the disadvantages of Fortran is that it caters to a very non-sexy segment of the programming population, by that I mean that UX/UI is not of great concern - a lot of learning materials I come across reflect that. Unless you find a textbook, you'll probably end up looking through undergraduate/graduate engineering lectures, or some presentation from a national lab (see below).
First question, do you have access to a compiler? On all Linux distros I'm aware of, you can get the gnu fortran compiler fairly easily (some variation of gcc-fortran, gfortran, etc.). On Windows you can either boot up a VM, or use something like the MinGW toolchain (https://sourceforge.net/projects/mingw-w64/), which is a port of gcc compilers to windows. I have no experience with Mac and/or BSD.
Some learning materials I just came across on Google, which don't seem to cater to people with prior Fortran experience:
http://www.hpc.lsu.edu/training/weekly-materials/2014-Spring...
https://www.tacc.utexas.edu/documents/13601/162125/fortran_c...
http://www.dsf.unica.it/~fiore/f03t.pdf
Some more advanced topics:
http://people.ds.cam.ac.uk/nmm1/fortran/paper_10.pdf
http://people.ds.cam.ac.uk/nmm1/fortran/paper_11.pdf
http://people.ds.cam.ac.uk/nmm1/fortran/paper_12.pdf
http://people.ds.cam.ac.uk/nmm1/fortran/paper_13.pdf
http://people.ds.cam.ac.uk/nmm1/fortran/paper_14.pdf
http://people.ds.cam.ac.uk/nmm1/fortran/paper_15.pdf
http://people.ds.cam.ac.uk/nmm1/fortran/paper_16.pdf
http://people.ds.cam.ac.uk/nmm1/fortran/paper_17.pdf
>First question, do you have access to a compiler?
Sure do. In fact I managed to fumble my way through writing a program to parameterize away piston-engined aircraft performance into an iterative solver a few years ago, but the lack of good/comprehensible language reference materials made trying to decipher the arcane incantations needed to do robust file I/O or divide the program into multiple source files, etc. into a very frustrating experience.
It was the last time I touched Fortran.
I'm surprised nobody has sat down and said "All right, if we want people to use Fortran outside of legacy codebases, we really need to polish the language introduction and put out a modern getting started guide to writing idiomatic Fortran in $current_year."
Because if they don't want new people to learn and use Fortran, why are they bothering to update the standard?
The fact that 'the standard' has a different meaning in Fortran compared to other languages is indeed frustrating (see 2008 standard conforming status of various compilers here [0] as of Apr 2016); however, that being said backwards compatibility is taken very seriously, so learning the basics of 90/95 isn't a waste compared to the later versions of the standard.
I'm not sure why there isn't a better focus on a thorough introduction. I learned Fortran as I have learned all languages - with great frustration. Fortran hasn't been any different for me in that regard.
BTW, here's a great resource I've found helpful in the past that does a great job of comparing Fortran to Python (e.g great if you have a numpy background).
[1] http://www.fortran90.org/index.html
I think for me, the problem is that my background is not in comp sci or software engineering. I am an aerospace engineer and am mostly self-taught save for what I retained from my first year C++ course back in the early aughts or gleaned from my sibling who is an ex Googler.
So I end up not grokking a lot of tutorials targeted at professional programmers, maybe?
I've written C, C++, C#, PHP, Python, lisp, Tcl, and Fortran and it was Fortran that presented the biggest hurdle in terms of finding quality info.
I sometimes wonder why nobody wrote a "Practical Common Lisp" for Fortran. Some artifact of the different characteristic Fortran user vs the Lisp user maybe?
If most fortran code looks like that then I think the language deserves a reputation for being arcane and cumbersome.
edit: That being said, there are some really well-written projects that have shown me what good modern Fortran looks like [1-3].
[1] https://github.com/nwukie/ChiDG
[2] https://github.com/flexi-framework/hopr
[3] https://github.com/jacobwilliams/Fortran-Astrodynamics-Toolk...
More recently, we've replaced Python, Ruby, and Java based systems with golang based ones. Not having to lug around a VM and associated other parts (jars, gems, ...) is a huge win. Performance is better across the board, and we've reduced the amount of hardware needed. There's also much better understanding of the code across the whole team.
The move to golang has been really interesting. The language really does seem to scale well with teams. Its great to be able to pick up even code that you would expect to be complicated, such as the golang tls library, and be able to understand it. The much easier to use concurrency and fantastic standard library mean there's a lot less looking around for which framework to use or which approach to apply.
"We switched from x to y. It was important because it justified rewriting the thing, which improved performance. Would have loved to rewrite it in x, but support just wasn't there."
This mostly due to the inherent complexity in Spring and the fact that Spring developers are also Spring experts. When the main developer behind the application left, we struggled to add new features or even fix bugs because the team lacked the Spring expertise.
The rest of the business mostly dealt with Scala, so it was almost a no-brainer to go with Play.
The outcome has been very surprising. The application has better performance overall, is better suited for streaming and we have much more expertise in-house to add features and fix bugs.
The re-write was not without pain though. Spring is a well-supported and very rich framework. It probably does a bunch of things that the casual web developer will likely forget.
My project is not so small, and the average developer is not interested in spending the time to really learn the language and functional style.
So in the end, we have code that not everyone reads and writes well, an ecosystem that is years behind Java, and tons of incompatibilities between Scala versions as it's still not stable. It's a mess, and most just end up writing Scala imperatively a la Java from 2005.
Java 8 is actually pretty good and has a lot of the power of Java without the complex features few use correctly. I'm hoping Kotlin continues improving, too.
If you go out on your own and decide to build your project with Scala and a big focus on using a functional style but no one else is following, you are going to have a bad time.
I'd like to know what are your issues with Scala and versions because we don't have these. IMHO, Scala is years ahead of Java and it's possibly so different that comparing them does not even make sense.
The transition from Java to Scala is awkward because you can do most of the things you do with Java in Scala. With that said, I think it's counter productive to do it. Scala is very different and approaching it with an imperative Java (even an OO) style is the worst thing to do.
One thing that I realized is that some people can go off and write cryptic code in Scala, very easily. This is true of other languages as well but in Scala, you can do a lot of things that will make you regret it the next time you try to read the code.
We wanted to upgrade some other - not all - projects to newer versions of 2.11.
It wouldn't work because our Thrift clients were incompatible with stubs compiled with 2.10. We didn't have the time and manpower to rewrite all the dependent services, and so we're stuck on 2.10.
This is something that'd never happen with even old-school SOAP or similar web services. It kind of left a bad taste in my mouth.
PHP's dynamic typing combined with Laravel's magical approach makes discoverability hard. A developer can't trace through a request by starting from a controller method and navigating through a codepaths with the support of their IDE. Our application code uses typehints almost exclusively, which helps. But whenever the code you're debugging drops into the framework (or PHP), you'll need to break out your browser and spend time a great deal of time reading documentation to understand how to use the function. For example, certain functions in Laravel accept no arguments in the function signature, but the function body calls PHP methods to dynamically parse function arguments.
We spend a fair amount of time documenting all the framework and language-level magic constructs. If we've dropped the ball on documentation (which happens often) a new developer is at the mercy of coworkers to explain where the framework (or language) magic happens.
On the plus side, Laravel's batteries-included approach significantly speeds our time to MVP.
Scala's category theory approach to functional programming is not easy for new developers to understand at first glance. While most of our code (framework or otherwise) is now easily navigable with an IDE, developers now need to spend time understanding concepts such as for comprehensions, monads and ADTs. However, most functional concepts are understandable without the help of coworkers, which means a new dev can rely on Google to help understand a concept, rather than relying on a coworker.
Once knowledge of syntax has been attained, Scala's strong type system makes development far easier. We can communicate semantics through types and monads (such as Either, Future, Option and domain-specific ADTs), and incorrect code is immediately flagged by the IDE. A new developer making a change to a database schema may now change a database column name, recompile, and be presented with a list of every bit of code they've broken.
Using types to represent the semantics of our domain has been incredibly powerful, and makes potential bugs much easier to spot when reading the code. For example, rather than checking a user's subscription status inside a method, we can require a "SubscribedUser" type in our method signature. With this type in place, a new developer can no longer accidentally call that method with an "UnsubscribedUser".
Perhaps most importantly, the long term benefits of Scala's strong type system are incredibly valuable. We're a software agency, so our large projects experience development in phases. It may be 6-12 months before our team circles back to a large project for major development. In that time, we've forgotten all the quirks and gotchas of that particular framework and language, and Scala's strong static type system significantly decreases regressions during the new development effort.
In summary, new developers have a similar learning curve for each language/framework. And in the end, Scala's long term maintainability is more valuable than Laravel's speed to MVP.
[1]https://www.coursera.org/specializations/scala
I'd recommend the book 'Scala for the Impatient', tutorials and projects from Twitter, and Maven or SBT template projects.
We began working with Scala very slowly, only using it for small internal projects until we were more familiar with the language and stack.
We also avoided heading too deep into category theory territory in the beginning. Libraries like Cats and Scalaz are not allowed (currently).
I have done a lot of business/enterprise development (a very hostile space to innovation and working solo or with very small teams), and have done small-to-largeish (from my POV) rewrites in several languages.
From:
- Fox 2.6 to Visual FoxPro. A breaking change in a lot of ways, a total win in the process. Not just because the app was native windows now.
- From Fox to Delphi. Now I discover the beauty of Pascal and improve the app and deployment scenario. Static types is a net win overall. My other love is python, probable code faster on it, but have FAR LESS trouble with strong type systems.
(However take a me some years in note how bad all languages are aside the DBase Family in talk with databases, but other wins distract me from that...)
- Visual Fox to .NET (1.0, 1.1 with both Visual Basic and C#) was a total net loss. A Massive increase in code size, yet the (desktop) apps were way slower than Visual FoxPro, even more than Delphi (but my boss not let me use Delphi).
The web was also terrible in performance and complexity. Sadly back in the day I was unaware of how do web properly and drink all the MS KoolAid on this.
This sink the project and almost the company. Only saved returning back to full FoxPro.
- To Python. I move several things to python, mainly .NET stuff. How boy, how big was the win. The net reduction in code size and the clarity of the code!
Also, (web) apps way faster. Take .NET some years in learn the way here, so...
- To RDBMS (Heck, even sqlite): Still big wins when someone else try to use a nosql/desktop datase (in my space, NOBODY is Facebook. With no exception, step-out of a RDBMS is one of the biggest mistakes)
- To F#: I return to .NET past year (because MS do a lot of the right moves to fix old mistakes!!!) and again a lot of reduction in code size, removing of problematic logic obscured by years of OO-only code. Still not happy about the way lower quality tooling, but enduring it even in Xamarin Mobile because I see the benefit.
I wish I could use swift for Android, so F#/.NET is my only sane option left...
----
Mainly, move from a lang to another that is not similar, help in see the problems with the old one. Learn new or better ways to solve stuff, and get access to different toolsets and mindsets. This payback when returning back to the old, too, when this ideas are migrated.
At my current employer's a big part of the codebase is in Perl and the boss is a fan of the language, so we keep using it. The problem is, Perl is pretty much dead, and most of the packages out there on CPAN feel like they've been built 10 years ago. Not to mention, the language itself lacks what I would call essential features like exception handling, classes, etc (which has to be tacked on by using "shims" from CPAN like Moose or Try::Tiny)
At some point I had a particular issue in one of our apps where it would be making tons of DB queries and we need to cache them. In Python land there are plenty of packages that give me transparent caching at the ORM level. In Perl land? Oh yeah this post on a mailing list from 2007 about someone having the same problem, and a bit of untested code that may or may not work.
I gave up on that particular issue and it'll probably never get fixed, but let's just say that have we been using an "alive" language things would've gone much smoother.
I don't know anything about the Python libraries as such but if they implement pluggable modules like memcache, redis, SysV SHM, etc., for the caching, then you can chop and change as required without having to make any application level changes[1]. Which is also handy for testing because you can supply your own mock cache module to do ... whatever.
[1] A good example would be going from "direct DB access" to "global memcache" to "local memcache backed by global memcache" - all without application changes.
I strongly disagree: I'd rather debug something wrong with the lone application level change which added the cache support at the application level, rather than have to enter the long and deep rabbit hole of the path the code takes at the point I ask for a resultset and I get something from the DB-backed ORM, or from a global memcache, or blah.
But then again, I happily develop on mostly-Perl codebase(s) ;)
… and they're getting fewer and fewer by the year, at least for Perl :|
With this obsolete language the only way is to change pretty much all the app to do app-layer caching and that would've cost 20x times that, so we're not going to do it.
Hence everything is being rewritten to Java(Script) and PHP.
In Python land I am still happily getting offers with good salaries.
tl;dr: Don’t build your app on top of a pile of crap in-house framework.
https://www.cloudbees.com/blog/about-paypals-node-vs-java-%E...
Dynamic tracing all the things has reduced the time to solve bugs by an order of magnitude for us.
Yes, you should write unit tests to cover this in interpreted languages with weak or no types, but this depends on the developer a) doing it, and b) not missing any cases - PRs/code reviews are not a catch-all. Especially in the case of factoring common logic out or some other form of refactoring, a strongly typed language is my best friend.