Programming Language Fragility
cancel.fm
cancel.fm
There is nothing in the C standard that requires dynamic linking. In fact, I have been creating statically linked binaries with C for decades (and still am).
Also the definition of portability used here is not helpful.
If I take a program and put it on another computer, I can never just expect it to work. The other computer may be running OpenBSD when the first one was running Linux and we are talking about a Windows EXE. This metric makes no sense.
The established meaning of portability is whether you can take the source code and compile it on a different platform (but also supporting a development environment for said programming language), and it will compile and work.
This is not a function of the programming language.
The "winners" of this blog post are winning because they contain compatibility layers. Those work as long as you have one for the target platform. If you don't you may actually be worse off with those languages compared to, say, a basic C environment that does not give you source code portability across all platforms but gives you a workable basis on each platform to bootstrap off of.
Also note that being portable may actually turn out to be a downside, because it means you have to limit what you can do to the common denominator of all supported platforms.
I recommend disregarding this blog post.
Even this meaning only applies to compiled languages. If I want to run Python code, I don't need to compile it and I don't need a laundry list of supporting tools for compiling and linking; I just need the Python interpreter. Which runs on all common platforms and plenty of uncommon ones.
Take your python example, you don't just need the interpreter. You also need any supporting infrastructure that the libraries used will need. As a trite example, anything that reads from a camera will need a camera available to work. Indeed, some will need specific cameras, if they don't have a portability later. :)
As such, portable goes across many layers and cannot be described at just the language level. Not even at a runtime level.
This will also be true of a program in a compiled language, so I don't see that it differentiates between different languages.
Portability has to take into account the scope of where it will be ported to. And, often, porting the software is something that has to be done. It will not be an intrinsic of the language or the program, but work that was done for them. Is why SCUMM is probably the most ported software. Was not necessarily an intrinsic property of the engine; though, it clearly had affordances that helped.
Really, I think Linux shot down a large part of the portability debate. It was made to be non portable. And now it has been ported everywhere.
Let's imagine that the author used a different term--say, distributability--and defined it as something like "how easy it is to get cross-platform software up and running on another system" and the context is "desktop and server computing" as opposed to embedded or whathaveyou. I think this is a perfectly useful criteria for evaluating programming languages (which isn't to say that it's the most important or even very important, only that it's useful).
It's useful to know whether I'm going to have a very easy time distributing software (e.g., a self-contained static binary) or a very hard time (e.g., I need to get my non-technical colleague to install brew, use brew to install the right version of python, create a virtualenv, install dependencies--probably including various C dependencies that pip doesn't know about, and then install my application). This isn't a hypothetical consideration; it's an important consideration for me whenever I'm creating internal tools.
However, to your point, this isn't helpful for figuring out which languages are going to be able to be deployed to the widest breadth of architectures. They're simply different ideas.
In my opinion (and extensive experience), I'd rather have something like Go where I can trivially cross compile for whatever `(arch, os)` tuple I want to target and send the resultant binary without concerning myself with runtime dependencies (including the runtime itself).
I had some Pascal code from 1994, when 16/32 bits ruled the world, I compiled/ran it in 64 bit glory, I did have to take out the one assembly optimization I wrote back then.
If there were Lazarus/Free Pascal for the new Apple Silicon, I have no doubt that code would work there as well, with zero changes.
Your statement only makes sense within a "libc is just the standard" worldview, which I believe contributes to the fragility problem by offloading compatibility into a series of "good-enough" measures instead of documentable specifications.
Most C builds default to dynamic linking, so while it's trivial it does take a few tweaks. Though that relies almost more heavily on the particular build system used with C. Actually in that regard Nim should be listed the same as C, as I had to pass a few C compiler options recently to allow running an executable compiled on Linux on amd64 to run on x86 Linux as well. So compared to Go's skipping of libc it'd seem to make sense. Maybe it'd be more obvious if the portability was an effort rating from 1-5. Go is 1, simple C is 2, Nim is a 2, and Rust is a 3, and complex/ancient C autoconf/make anywhere from 2-5. Elixir 2-3, Python 4-5. Based largely on the googling efforts I've done in the past.
Regarding the compatibility layer though, the bootstrapping makes sense. Generally C and C compilers are going to be imminently more portable since some variant of C is likely yo be available. Python's standard library is likely going to be an enormous effort to port. Perl/Lua much less so but at the expense of the compatibility layer meaning more individual platform work.
Most compatibility layers are going to be a pain or limiting as you say. I'd like to say I'm a fan of Nim's "open architecture" standard library (compatibility layer) having recently done a PR to add support for FreeRTOS. Much of the stdlib worked without modification due to "lazy compiling" of code, including the stdlib. It only tries compiling what you use. Skip the default networking and write a few C/C++ FFI calls networking for the platform and you've got a full library of containers, json parsing, etc. Then you can pretty easily add support for Nim networking compatibility later. For example the 'selector' library has support for various OS specific from ancient 'select' to 'epoll' to whatever Windows does based on what you choose to import. Pretty awesome for embedded.
Overall it seems the article is considering portability in two narrow contexts. However, having both tried to ship say a scientific GUI program for non-technical users (e.g. using Python QT) on one hand the first definition is useful, while porting code to embedded projects the other can be useful.
The criterion the author uses is:
> If you write some software in a particular language, can you copy the compiled program to another computer and expect it to work?
I coalesced the two columns the author used because it was "No" for both in the four examples below:
> Java No Complex and fragile bundling of a separate runtime (with licensing issues)
> C#/F#/.NET No Complex and fragile bundling of a separate runtime (with licensing issues)
> Python No Complex and fragile bundling of a separate runtime
> Ruby No Complex and fragile bundling of a separate runtime
I don't think that's how we generally think about portability. We usually consider installing the runtime as the platform dependent tax you pay to get your compile-once artifacts. Compiled JARs are generally considered portable. .NET Core binaries are generally considered portable. Python and Ruby scripts are generally considered portable.
Requiring that the application be portable by only copying the files across (without installing the runtime or platform abstraction layer) isn't representative of how users or even dev ops teams that handle mixed-platform carry programs across platforms in the first place.
If that's the criterion the author is using then I don't understand how Rust got a "no". Rust cross-compiling is also trivial and I can compile on one machine for a completely different one and send only the binary output.
Now, either you care or you don't. If you don't need portability to every CPU made since 1980, then the difference doesn't matter to you, and Java, C#, and such are portable for everything that you want.
I'm not saying this to slam python, as I use it, but it's one thing I find attractive about certain other languages (such as those described as more portable in the linked piece).
There's a certain honesty about the linked piece that in the very least seems reasonable, even if you disagree with it. As you say, it depends on your idea of "portability."
However, you could use gcj (for java < 1.4? Not sure) to create standalone systems. And apparently, you can throw java at LLVM to get runnable binaries. So maybe java doesn't even need a runtime?
And on the other hand, once JNA/JNI comes into play, portability of jar files becomes ... interesting. This is similar to the usual hell of python with C dependencies.
All in all, I don't understand the criteria from the article enough to evaluate that knowledge into a statement.
I think it's more useful to look at different use cases and possibilities - like having a GC or not and which deployment targets that affects, the possibility for static and dynamic linking, etc.
For portability, they ask: "If you write some software in a particular language, can you copy the compiled program to another computer and expect it to work?"
This is the wrong question, but it also is answered incorrectly. You can certainly write JavaScript, Java, C#, on most modern platforms and have little trouble porting them. Java, as others have mentioned, is fully portable with JAR files. Node.js is literally JavaScript, which can run anywhere there's a JavaScript engine for the most part (with some asterisks for Node.js's particular flavor). C# compiles to an IL that can be used anywhere, so the parts of the code we're talking about as "not portable" are the binaries wrapped around that.
For compilers, they ask: "If the internet stopped functioning, would you be left helpless?"
By these criteria, their assessment of compilers is mostly accurate. But I'm not sure what they mean by "acquiring or setting up" the compiler? Presumably that means "copy the compiler manually to another computer" because this is a world with no Internet?
In that case, the better question is "can I get an offline installer for these development environments", which is a fairly commonly available thing. Package restore is a bigger question, for those that use packages - but if you have the original binaries, you implicitly have the binaries for all the packages, so not overcomable.
I think what this person really means by "fragility" is "survivability" or "disaster preparedness." That's an area of software work that's often undervalued, and I agree that if you are evaluating software on that basis, many of the languages listed with the tools listed are poor choices. But that's such a distinct area of software that its general applicability is limited, in my view.
https://iohk.io/en/blog/posts/2020/07/28/improving-haskells-...
It is true that getting static Linux builds is nontrivial - but definitely doable [1]! But the demand is there so I'm guessing the elbow grease will follow in the coming few years :)
[1] https://www.joachim-breitner.de/blog/776-Distributing_Haskel...
It assumes that Linux distributions do not exist and we deploy applications by coping an executable around like people did in 1980.
In reality I never had such "fragility" problem around Python.
Some distributions did an excellent job at ensuring that all the dependencies are met at build time and that the build is deterministic and reproducible.
And that the same dependencies will be available for 5+ years without breaking changes - while also receiving security fixes.
This is not-so-subtle propaganda.
This is weird, because Nim (AFAIK) still requires a C compiler like GCC or MSVC to do the final compilation.
In fact, you can see how your Nim code is translated into C, something like this: nim c helloworld.nim
Dunno if I agree with the second column. .NET Core is cross platform now, but I'm not sure what fragile bundling really means here, but it definitely is portable with "effort".