33 comments

[ 2.6 ms ] story [ 87.2 ms ] thread
I haven't read it in-depth, but it's funny that Perl is marked as "not portable by default". But it runs on everything from z/OS, to VMS, to Unix, DOS, Windows, etc. By default. I do agree that it involves some pretty complex build and install tools and scripts.
What a hatchet job. Java software (JAR files) not portable and with licencing issues? It's like OpenJDK never happened.
I may be missing the point here but this postings makes little sense to me.

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.

> The established meaning of portability is whether you can take the source code and compile it on a different platform

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.

This is easy to tease out. Portability can be describing compilation or evaluation/interpretation.

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.

> You also need any supporting infrastructure that the libraries used will need.

This will also be true of a program in a compiled language, so I don't see that it differentiates between different languages.

I felt I was progressing your point. You can't answer if a program is portable based solely on the language.
You’re referring to a different definition of “portables than the concept TFA appears to be describing. Within the scope of TFA’s definition, a language (or more precisely, an implementation) absolutely can be portable. Any implementation that can bundle all dependencies (including the runtime) into a single artifact can said to be portable per TFA’s definition of the term. Python could even be portable in this sense if it could zip up the interpreter, libraries, and all so files (however, these bundles would be hundreds of megabytes compressed in practice).
But that is a shit definition. :)

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.

Again, I think you're just insisting on evaluating the article based on your own definition of "portability". Note that I agree that your definition is more standard, and the confusion is attributable to the author choosing to overload that term.

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.

Fair. I get what the article is aiming to talk to. I would still pull up Linux as a fun counter example. One of the more ported code based ever. Not even really portable between compilers of the language it is written in.
You need the Python interpreter and all of your dependencies including the many shared objects that Python programs tend to depend on. Moreover, having the right version of the Python interpreter installed is frequently a problem--lots of systems still have Python 2 installed by default and even having Python 3 isn't a guarantee since many programs depend on relatively recent versions of the interpreter which are rarely installed by default on the target system. Note also that while the Python interpreter runs on plenty of uncommon platforms, relatively few packages in the ecosystem do because the ecosystem depends so heavily on C-extensions and `pip install`s of these packages often need to build from source which often assumes you have various headers and libraries installed on your system at certain locations--which is to say the assumption is that you're pip installing onto a "common platform".

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 tried pulling a C program from 2002 into the present world, it was a nightmare. C assumes word size, Macro functioning and the libraries are all radically different, it wouldn't even compile. C is definitely not portable.

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.

Regarding linking: you seem to be conflating concerns with libc's portability with the linking of libc. Libc is a portability concern not because of linking but because it is a worse-is-better compatibility layer. There isn't just one example of it, and it straddles the boundaries of the language ecosystem and the operating environment. If I write C targeting WASM and try to call fopen(), the build system either has to say "no" somewhere along the way, or bamboozle me with a very specific interpretation of what I mean by that, effectively turning it into an unspecified interface. When a language builds on that foundation it gains temporary convenience but misses out on being more encompassing in its approach to compatibility.

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.

The list certainly is confusing. It took reading it a couple of times, but now it makes more sense what they appear to be basing the list on.

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.

>C#/F#/.NET - No (complex runtime) - No (fragile bundling)

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".

"Fragility" is an interesting concept, but I don't agree with the criteria used here. There are also some severe misunderstandings of how the evaluated languages work.

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.

The portability category has some non-intuitive results. By that I mean that I think they're wrong, but it's a matter of how you measure "portability".

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.

A lot of people are getting hung up on the definition of 'portability'. Yes, the author's definition is pretty obviously using a non-standard definition, but the question is whether the concept he's talking about is helpful. And I think it is--I would much rather have a language like Go where cross compiling is trivial and I can just send my colleagues (even my non-technical colleagues!) a binary file compared to having them install the right version of the runtime and the right dependencies (and probably a virtualenv so it doesn't bungle their system) and then the application code. I don't care very much about whether I can run a single set of artifacts on any target if it's burdensome to prepare the target platform--I'd much rather it be trivial to compile a single artifact for the target platform such that it can just run.
> Go where cross compiling is trivial

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.

There's portability, and there's portability. You can run a jar file on anything that has a JVM. You can run C code on an 8085 microcontroller if you have a cross-compiler and a way to move the file. So C code is portable to a whole world beyond the "mixed-platform" world that you're thinking of.

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.

To be clear, I think TFA is using an altogether different definition. These are all distinct concepts with value in their own right—it’s unfortunate that they’re overloading the same term.
Python is an interesting case to me because of my major complaints about it is how unportable it is in practice. In theory, sure, a python script should be portable, but in my experience the level of dependency hell involved can be extreme, and it's not uncommon with what I've done to see a developer specify some baroque stack to get something to work. This in fact is one of the reasons I've seen for using things like conda, so that you can create different environments with different versions of different packages for different applications. This just gets worse when you consider the edge cases for things like pypy etc.

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."

This becomes even more complex in the case of java, at least I'm the most familiar with java. And yes, at work, we are distributing jar/war-files built on linux to linux and windows targets, and back in the day to solaris as well. BSD has been prototyped as well on a late friday night. That was funny.

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.

Not to make this too topical, but I wonder how much this has to do with why I see so many software developers using macbooks. Developing on one is closer to developing on a Linux machine, which is where the code will frequently deploy to. Then, with the rise of less portable languages like python, Microsoft felt the need to invest in the WSL.
The standard way to make Rust portable would be to statically link with musl, not necessarily use no_std. It depends if you mean for embedded devices though (although Go is listed as portable, so I guess not).

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.

(comment deleted)
The tables says that Nim is both portable by default and with effort. C only portable with effort.

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

Nim uses a C compiler, but produces dependency-free executables. This is not something you can do easily in C alone, unless you want to write your own standard lib. (Or on Linux, try to use musl, but that won't always work depending on what you want to do.)
The opposite seems true on M1 support. C is always #1 followed by LLVM ecosystem languages, then the VMs.
The article takes an absurd angle at "fragility".

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.

Apparently Go is good and C, C++, Java, Python, Perl, gcc, clang are all "fragile".

This is not-so-subtle propaganda.