1. 2

    It seems like the idea of putting a version number in a header was already well-established when IP was first defined. I wonder who was the first to come up with the idea of versioning?

    1. 3

      I imagine versioning 0.0.1 probably revolved around file.txt, file_2.txt, file_FINISHED.txt, file_FINISHED(1).txt, file_FINAL_REAL.txt ad infinitum…

      1. 9

        It’s quite possible they wrote the specifications on a DEC system with automatic file versioning. Probably more like DOC.TXT;69 incrementing as they make new versions.

        1. 2

          More information on DEC automatic file versioning: Retrocomputing Stack Exchange – Filesystems with versioning – answer

          1. 1

            Fun! I didn’t realize that was a thing!

      1. 1

        I agree that the tests the author replaced were highly brittle. And the author’s new architecture is much better. And the author’s new tests are much less brittle.

        But I think the author can go even further with removing mocks - why do “command.ShouldEqual(AuctionCommand.Bid(3));” instead of just receiving that auction event in the XMPP server? Why do “sniper.StateShouldBe(SniperState.Bidding, 1, 3);” rather than query the WPF interface? That is, why have a programmatic interface for the auction sniper at all - why not use it only through its actual external interfaces, WPF and XMPP?

        1. 3

          Faster test suites, and tests are easier to write overall. You still need end to end tests like you mention but they don’t have to cover every edge case of domain logic.

        1. 14

          What’s going on here? How did this get to the top of lobste.rs with 26 upvotes? I’m happy for the OP that they could get their system to work, but as far as I can tell, the story here is “package manager used to manage packages.” We have been doing that for decades. Is there any way the community can get a lever to push back on thin stories like this one?

          1. 25

            Would it change your opinion if the article mentioned that the nix shell being used here is entirely disposable and this process leaves no mark in your OS setup? Also that even if this required some obscure versions of common system dependencies you could drop into such a shell without worrying about version conflicts or messing up your conventional package manager?

            I agree that the article is thin in content, but I don’t think you can write this story off as “package manager used to manage packages.” , I think nix shell is very magical in the package management world.

            1. 6

              I could do that with docker too and it would not leave a trace either

              1. 17

                Yes, but then you’d be inside a container, so you’d have to deal with the complexities of that, like mounting drives, routing network traffic etc. With nix shell, you’re not really isolated, you’re just inside a shell session that has the necessary environment variables that provide just the packages you’ve asked for.

                Aside from the isolation, the nix shell is also much more composable. It can drop you into a shell that simultaneously has a strange Java, python and Erlang environment all compiled with your personal fork of GCC, and you’d just have to specify your GCC as an override for that to happen.

                1. 4

                  I get that, but I have to go through the learning curve of nix-shell, while I already know docker, since I need it for my job anyway. I am saying that there are more ways to achieve what the article is talking about. It is fine that the author is happy with their choice of tools, but it is very unremarkable for the title and given how many upvotes that article got.

                  1. 5

                    Why not learn nix and then use it at work as well :) Nix knows how to package up a nix-defined environment into a docker container and produce very small images, and you don’t even need docker itself to do that. That’s what we do at work. I’m happy because as far as I’m concerned Nix is all there is and the DevOps folks are also happy because they get their docker images.

                    1. 3

                      I work in a humongous company where the tools and things are less free to choose from atm, so even if I learned nix, it would be a very tough sell..

                2. 3

                  As someone who hasn’t used Docker, it would be nice to see what that looks like. I’m curious how the two approaches compare.

                  1. 6

                    I think that the key takeaway is that with Docker, you’re actually running a container will a full-blown OS inside. I have a bias against it, which is basically just my opinion, so take it with a grain of salt.

                    I think that once the way to solve the problem of I need to run some specific version of X becomes let’s just virtualize a whole computer and OS because dependency handling is broken anyway, we, as a category simply gave up. It is side-stepping the problem.

                    Now, the approach with Nix is much more elegant. You have fully reproducible dependency graphs, and with nix-shell you can drop yourself in an environment that is suitable for whatever you need to run regardless of dependency conflicts. It is quite neat, and those shells are disposable. You’re not running in a container, you’re not virtualizing the OS, you’re just loading a different dependency graph in your context.

                    See, I don’t use Nix at all because I don’t have these needs, but I played with it and was impressed. I dislike our current approach of just run a container, it feels clunky to me. I think Docker has it’s place, specially in DevOps and stuff, but using it to solve the I need to run Python 2.x and stuff conflicts with my Python 3.x install is not the way I’d like to see our ecosystem going.


                    In the end, from a very high-level, almost stratospheric, point-of-view: both docker and nix-shell workflow will be the developer typing some commands on the terminal, and having what they need running. So from a mechanical standpoint of needing to run something, they’ll both solve the problem. I just don’t like how solving things by doing the evergreen is now the preferred solution.

                    Just be aware that this is an opinion from someone heavily biased against containers. You should play with both of them and decide for yourself.

                    1. 3

                      This comment is a very good description of why I’ve never tried Docker (and – full disclosure – use Nix for things like this).

                      But what I’m really asking – although I didn’t make this explicit – is a comparison of the ergonomics. The original post shows the shell.nix file that does this (although as I point out in another comment, there’s a shell one-liner that gets you the same thing). Is there an equivalent Dockerfile?

                      I was surprised to see Docker brought up at all because my (uninformed) assumption is that making a Docker image would be prohibitively slow or difficult for a one-off like this. I assumed it would be clunky to start a VM just to run a single script with a couple dependencies. But the fact that that was offered as an alternative to nix-shell makes me think that I’m wrong, and that Docker might be appropriate for more ad-hoc things than I expected, which makes me curious what that looks like. It points out a gap in my understanding that I’d like to fill… with as little exertion of effort as possible. :)

                      1. 4

                        But the fact that that was offered as an alternative to nix-shell makes me think that I’m wrong, and that Docker might be appropriate for more ad-hoc things than I expected, which makes me curious what that looks like. It points out a gap in my understanding that I’d like to fill… with as little exertion of effort as possible. :)

                        I think containers is a perfectly capable solution to this. The closest thing you can use would probably be toolbox.

                        https://github.com/containers/toolbox

                        It would allow you to even provide a standardized environment which would be decoupled from the deployment itself (if that makes sense). It also mount $HOME as well.

                        1. 3

                          I use Nix, but also have experience with Toolbox.

                          I would recommend most people to use Toolbox over nix-shell. With toolbox you can create one-off containers in literally seconds (it’s two commands). After entering the container you can just dnf install whatever you need. Your home directory gets mounted, so you do not have to juggle with volumes, etc. If you need to create the same environment more often, you can create a Dockerfile and build your toolbox containers with podman. The upstream containers that Fedora provides are also just built using Dockerfiles.

                          The post shows a simple use case, but if you want to do something less trivial, it often entails learning Nix the language and nixpkgs (and all its functions, idioms, etc.). And the Nix learning curve is steep (though it is much simpler if you are familiar with functional programming). This makes the toolbox approach orders of magnitude easier for most people - you basically need to know toolbox create and toolbox enter and you can use all the knowledge that you already have.

                          However, a very large shortcoming of toolbox/Dockerfiles/etc. is reproducibility. Sure, you can pass around an image and someone else will have the same environment. But Nix allows you to pin all dependencies plus the derivations (e.g. as a git SHA). You can give someone your Nix flake and they will have exactly the same dependency graph and build environment guaranteed.

                          Another difference is that once you know Nix, it is immensely powerful for defining packages. Nix is a turing-complete functional language, so nixpkgs can provide a lot of powerful abstractions. I dread every time I have to create/modify and RPM spec file, because it is so primitive compared to making a Nix derivation.

                          tl;dr: most people will want to use something like Toolbox, it is familiar and provides many of the same benefits as e.g. nix-shell (isolated, throw-away environments, with your home directory available). However, if you want strong reproduciblity across systems and a more powerful packaging/configuration language, learning Nix is worth it.

                        2. 3

                          A cool aspect of Docker is that it has a gazillion images already built and available for it. So depending on what you need, you’ll find a ready-made image you can put to good use with a single command. If there are no images that fill your exact need, then you’ll probably find an image that is close enough and can be customised. You don’t need to create images from scratch. You can remix what is already available. In terms of ergonomics, it is friendly and easy to use (for these simple cases).

                          So, NixPkgs have a steeper learning curve in comparison to dockerfiles. It might be simpler to just run Docker. What I don’t like is what is happening inside Docker, and how the solution for what looks like simple problems involves running a whole OS.

                          I’m aware that you can have containers without an OS like described in this thread, but that is not something I often see people using in the wild.

                        3. 1

                          Nit-pick: AFAIK one doesn’t really need Alpine or any other distro inside the container. It’s “merely” for convenience. AFAICT it’s entirely possible to e.g. run a Go application in a container without any distro. See e.g. https://www.cloudbees.com/blog/building-minimal-docker-containers-for-go-applications

                    2. 3

                      Let’s assume nix shell is actual magic — like sourcerer level, wave my hand and airplanes become dragons (or vice versa) magic — well this article just demonstrated that immense power by pulling a coin out of a deeply uncomfortable kid’s ear while pulling on her nose.

                      I can’t speak for the previous comment’s author, but those extra details, or indeed any meat on the bones, would definitely help justify this article’s otherwise nonsensical ranking.

                      1. 2

                        Yeah, I agree with your assessment. This article could just as well have the title “MacOS is so fragile, I consider this simple thing to be an issue”. The trouble with demonstrating nix shell’s power is that for all the common cases, you have a variety of ad-hoc solutions. And the truly complex cases appear contrived out of context (see my other comment, which you may or may not consider to be turning airplanes into dragons).

                    3. 19

                      nix is not the first thing most devs would think of when faced with that particular problem, so it’s interesting to see reasons to add it to your toolbox.

                      1. 9

                        Good, as it is not supposed to be the first thing. Learning a fringe system with a new syntax just to do something trivial is not supposed to be the first thing at all.

                      2. 4

                        I find it also baffling that this story has more upvotes than the excellent and original code visualization article currently also very high. Probably some nix up vote ring pushing this

                        1. 12

                          Or folks just like Nix I guess? 🤷

                          1. 11

                            Nix is cool and people like it.

                            1. 5

                              I didn’t think this article was amazing, but I found it more interesting than the code visualization one, which lost me at the first, “From this picture, you can immediately see that X,” and I had to search around the picture for longer than it would have taken me to construct a find command to find the X it was talking about.

                              This article, at least, caused me to say, “Oh, that’s kind of neat, wouldn’t have thought of using that.”

                            2. 6

                              This article is useless. It is way simpler (and the python way) to just create a 2.7 virtualenv and run “pip install psycopg2 graphwiz”. No need to write a nix file, and then write a blog post to convince yourself you didn’t waste your time!

                              Considering all nix posts get upvoted regardless of content, it’s about time we have a “nix” tag added to the site.

                              1. 14

                                This article is not useless just because you don’t see its value.

                                I work mainly with Ruby and have to deal with old projects. There are multiple instances where the Ruby way (using a Ruby version manager) did not work because it was unable to install an old Ruby version or gem on my new development machine. Using a nix-shell did the job every time.

                                just create a 2.7 virtualenv and run “pip install psycopg2 graphwiz”

                                What do you do if this fails due to some obscure dependency problem?

                                1. 4

                                  What do you do if this fails due to some obscure dependency problem?

                                  Arguably you solve it by pinning dependency versions in the pip install invocation or requirements.txt, as any Python developer not already using Nix would do.

                                  This article is not useless just because you don’t see its value.

                                  No, but it is fairly useless because it doesn’t do anything to establish that value, except to the choir.

                                  1. 2

                                    In my experience there will be a point where your dependencies will fail due to mismatching OpenSSL, glibc versions and so on. No amount of pinning dependencies will protect you against that. The only way out is to update dependencies and the version of your language. But that would just detract from your goal of getting an old project to run or is straight up impossible.

                                    Enter Nix: You pin the entire environment in which your program will run. In addition you don’t pollute your development machine with different versions of libraries.

                                    1. 3

                                      Arguably that’s just shifting the burden of effort based on a value judgement. If your goal is to get an old project to run while emphasizing the value of incurring zero effort in updating it, then obviously Nix is a solution for you and you’ll instead put the effort into pinning its entire runtime environment. If, however, your value to emphasize is getting the project to run then it may well be a more fruitful choice to put the effort into updating the project.

                                      The article doesn’t talk about any of the hairier details you’re speaking to, it just shows someone taking a slightly out of date Python project and not wanting to put any personal effort into updating it… but updating it by writing a (in this case relatively trivial) Python 3 version and making that publicly available to others would arguably be the “better” solution, at least in terms of the value of contributing back to the community whose work you’re using.

                                      But ultimately my argument isn’t with the idea that Nix is a good solution to a specific problem, it’s that this particular article doesn’t really make that point and certainly doesn’t convincingly demonstrate the value of adding another complex bit of tooling to the toolkit. All the points you’ve raised would certainly help make that argument, but they’re not sadly not present in this particular article.

                                  2. 1

                                    Just out of curiosity, I’m also dealing with ancient ruby versions and use nix at work but I couldn’t figure out how to get old enough versions, is there something that helps with that?

                                      1. 1

                                        Thank you, very helpful!

                                        1. 1

                                          Do note this method will get you a ruby linked to dependencies from the same checkout. In many cases this is what you want.

                                          If instead you want an older ruby but linked to newer libraries (eg, OpenSSL) there’s a few extra steps, but this is a great jumping off point to finding derivations to fork.

                                          1. 1

                                            Do note this method will get you a ruby linked to dependencies from the same checkout. In many cases this is what you want.

                                            Plus glibc, OpenSSL and other dependencies with many known vulnerabilities. This is fine for local stuff, but definitely not something you’d want to do for anything that is publicly visible.

                                            Also, note that mixing different nixpkgs versions does not work when an application uses OpenGL, Vulkan, or any GPU-related drivers/libraries. The graphics stack is global state in Nix/NixOS and mixing software with different glibc versions quickly goes awry.

                                      2. 2

                                        This comment mentions having done something similar with older versions by checking out an older version of the nixpkgs repo that had the version of the language that they needed.

                                        1. 2

                                          Like others already said you can just pin nixpkgs. Sometimes there is more work involved. For example this is the current shell.nix for a Ruby on Rails project that wasn’t touched for 5 years. I’m in the process of setting up a reproducible development environment to get development going again. As you can see I have to jump through hoops to get Nokogiri play nicely.

                                          There is also a German blog post with shell.nix examples in case you need inspiration.

                                      3. 4

                                        this example, perhaps. I recently contributed to a python 2 code base and running it locally was very difficult due to c library dependencies. The best I could do at the time was a Dockerfile (which I contributed with my changes) to encapsulate the environment. However, even with the container standpoint, fetching dependencies is still just as nebulous as “just apt install xyz.” Changes to the base image, an ambiently available dependency or simply turning off the distro package manager services for unsupported versions will break the container build. In the nix case, it is sort of forced on the user to spell it out completely what the code needs, combine with flakes and I have a lockfile not only for my python dependencies, but effectively the entire shell environment.

                                        More concretely, at work, the powers to be wanted to deploy python to an old armv7 SoC running on a device. Some of the python code requires c dependencies like openssl, protobuf runtime and other things and it was hard to cross compile this for the target. Yes, for development it works as you describe, you just use a venv, pip install (pipenv, poetry, or whatever as well) and everything is peachy. then comes to deployment:

                                        1. First you need to make a cross-compiled python interpreter, which involves first building the interpreter for your host triple then rebuilding the same source for the target host triple making sure to tell the build process where the host triple build is. This also ignores that some important python interpreter things may not build, like ctypes.
                                        2. Learn every environment variable you need to expose to the setup.py or the n-teenth build / packaging solution for the python project you want to deploy, hope it generates a wheel. We will conveniently ignore how every C depending package may use cmake, or make, or meson, etc, etc…
                                        3. make the wheels available to the image you actually ship.

                                        I was able to crap out a proof-of-concept in a small nix expression that made a shell that ran the python interpreter I wanted with the python dependencies needed on both the host and the target and didn’t even have to think. Nixpkgs even gives you cross compiling capabilities.

                                        1. 1

                                          Your suggested plan is two years out of date, because CPython 2.7 is officially past its end of life and Python 2 packages are generally no longer supported by upstream developers. This is the power of Nix: Old software continues to be available, as if bitrot were extremely delayed.

                                          1. 3

                                            CPython 2.7 is available in debian stable (even testing and sid!), centos and rhel. Even on MacOS it is still the default python, that ships witht he system. I don’t know why you think it is no longer available in any distro other than nix.

                                      1. 11

                                        The software that’s actually used for the largest distributed systems maintained by the largest organizations isn’t available, even though individual services within it are open source.

                                        That’s because those systems are exploitative and proprietary.

                                        1. 1

                                          I don’t disagree - that’s basically my entire point. Was that not clear?

                                        1. 25

                                          How has AGPL failed? Quoting the introduction to Google’s own policies, which are the top hit for “agpl google” on DuckDuckGo:

                                          WARNING: Code licensed under the GNU Affero General Public License (AGPL) MUST NOT be used at Google. The license places restrictions on software used over a network which are extremely difficult for Google to comply with.

                                          This seems like a resounding success of AGPL.

                                          Proprietary distributed systems frequently incorporate AGPL software to provide services.

                                          Who, and which software packages? This isn’t just about naming and shaming, but ensuring that those software authors are informed and get the chance to exercise their legal rights. Similarly, please don’t talk about “the legal world” without specific references to legal opinions or cases.

                                          I feel like this goes hand-in-hand with the fact that you use “open source” fourteen times and “Free Software” zero times. (The submitted title doesn’t line up with the headline of the page as currently written.) This shows an interest in the continued commercialization of software and exploitation of the commons, rather than in protecting Free Software from that exploitation.

                                          1. 8

                                            How has AGPL failed?

                                            I said how in the article:

                                            The AGPL was intended, in part, to guarantee this freedom to users, but it has failed. Proprietary distributed systems frequently incorporate AGPL software to provide services. The organizations implementing such systems believe that as long as the individual process that provides the service complies with the AGPL, the rest of the distributed system does not need to comply; and it appears that the legal world agrees.

                                            The purpose of the AGPL is not to stop commercial users of the software, it’s to preserve the four freedoms. It doesn’t preserve those freedoms in practice when it’s been used, so it’s a failure.

                                            But really AGPL doesn’t have anything to do with this. No-one claims AGPL is a license like the one I describe in the article.

                                            Proprietary distributed systems frequently incorporate AGPL software to provide services.

                                            Who, and which software packages?

                                            mongodb is a high-profile example, used by Amazon.

                                            1. 10

                                              I’m fairly certain that the version of mongodb that Amazon based DocumentDB off of was Apache licensed, so I don’t think that applies here. From what I’m seeing, they also explicitly don’t offer hosted instances of the AGPL licensed versions of Mongo.

                                              1. 5

                                                But really AGPL doesn’t have anything to do with this. No-one claims AGPL is a license like the one I describe in the article.

                                                Then your article headline is misleading, is it not?

                                                1. 2

                                                  This is true. MongoDB changed their license, in response to Amazon using forks of their own software to compete with them.

                                                  1. 1

                                                    That’s fair, you’re probably right. There are lots of other hosted instances of MongoDB that used the AGPL version though, so MongoDB is still the highest-profile example of this. That was the motivation for MongoDB’s move to SSPL.

                                                  2. 2

                                                    I don’t see a laundry list here. I appreciate that you checked your examples beforehand and removed those which were wrong, but now there’s only one example left. The reason that I push back on this so heavily is not just because I have evidence that companies shun AGPL, but because I personally have been instructed by every employer I’ve had in the industry that AGPL code is unacceptable in their corporate environment, sometimes including AGPL developer tools! It would have been grounds for termination at three different employers, including my oldest and my most recent employments.

                                                    Regarding MongoDB, I have no evidence that AWS violated the terms of the AGPL, and they appear to have put effort into respecting it somewhat. It seems that MongoDB’s owners were unhappy that their own in-house SaaS offering was not competing enough with others, and they chose licensing as their way to fight. Neither of these companies are good, but none of them appear to be disrespecting the AGPL.

                                                  3. 4

                                                    the agpl says

                                                    Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software.

                                                    in other words, you cannot run your own proprietary fork of an agpl program; you have to offer users the modified sources. it says nothing about the sources of the other programs that that program communicates with, or the network infrastructure and configuration that comprises your distributed system.

                                                    1. 3

                                                      Yes. This is how we define distributed systems, in some security contexts: A distributed system consists of a patchwork network with multiple administrators, and many machines which are under the control of many different mutually-untrusting people. Running AGPL-licensed daemons on one machine under one’s control does not entitle one to any control over any other machines in the network, including control over what they execute or transmit.

                                                      Copyright cannot help here, so copyleft cannot help here. Moreover, this problematic layout seems to be required by typical asynchronous constructions; it’s good engineering practice to only assume partial control over a distributed system.

                                                      1. 1

                                                        So, that seems fine then? What’s the problem with that?

                                                        1. 1

                                                          the problem, as the article says, is that we have no copyleft license that applies to entire distributed systems, and it would be nice to. not so much a problem with the agpl as an explanation of why it is not the license the OP was wishing for.

                                                          1. 4

                                                            I explain more upthread, but such licenses aren’t possible in typical distributed systems. Therefore we should not be so quick to shun AGPL in the hopes that some hypothetical better license is around the corner. (Without analyzing the author too much, I note that their popular work on GitHub is either MIT-licensed or using the GH default license, instead of licenses like AGPL which are known to repel corporate interests and preserve Free Software from exploitation.)

                                                            1. 2

                                                              We have Parity as a start. Its author says:

                                                              “Parity notably strengthens copyleft for development tools: you can’t use a Parity-licensed tool to build closed software. “

                                                              Its terms are copyleft for other software you “develop, operate, or analyze” with licensed software. That’s reads broad enough that anything an operator of distributed systems both owns and integrates should be open sourced.

                                                      1. 2

                                                        One should short circuit the process by just sticking to hard coding, IMO. http://catern.com/config.html

                                                        1. 1

                                                          It’s a useful separation to have the same binary running two different configurations (prod vs staging vs. dev, etc.). I’d go as far as to say that if you don’t use that flexiblity for a given config variable, then you can gradually migrate it into the code. In other words, configuration has a place (IME more often than not), but if you have a small/simple system, it’s true you might not need it at all.

                                                          1. 3

                                                            It’s a useful separation to have the same binary running two different configurations (prod vs staging vs. dev, etc.)

                                                            Why is that? You can have the exact same libraries with a different main function - what does it matter if the main function is different? “A different main function” is the same as “a different configuration”.

                                                            Any property you could enforce by constraining what the configuration can do, you could enforce by constraining the interface of the libraries the main function is calling (I discuss this in http://catern.com/config.html as linked above)

                                                            1. 1

                                                              So are you saying that if you need have a web server that can listen on either port 80 or port 8888, you would build 2 different binaries with 2 different main functions for that?

                                                              If so, that seems silly for a whole bunch of reasons :)

                                                              One reason is that often there are admins/operators who run binaries and want to reconfigure them without rebuilding from source. Another reason is that deploying binaries to large clusters can be surprisingly expensive (e.g. to the point where people use BitTorrent to optimize it). Deploying configuration is cheap by comparison.

                                                              Those are 2 reasons, but there are many more, like being able to do a gradual (non-atomic) upgrade of clusters via config changes (something I asserted your last blog posted ignored). Fast rollbacks are another reason, etc.

                                                              1. 1

                                                                One reason is that often there are admins/operators who run binaries and want to reconfigure them without rebuilding from source.

                                                                Is your concern that rebuilding from source is slow? If so, use an interpreted language or rely on fast incremental builds.

                                                                Is your concern that admins/operators shouldn’t be writing code? I don’t think there’s a principled reason for that… even if they don’t know how to program it should be no harder for them to compose some libraries together than it is to write configurations (the code involved should be basically the same either way) - and they’ll get much better error messages too.

                                                                Another reason is that deploying binaries to large clusters can be surprisingly expensive (e.g. to the point where people use BitTorrent to optimize it). Deploying configuration is cheap by comparison.

                                                                Do you mean purely in terms of size? But a binary should be smaller than a (human readable) configuration, because e.g. it can store {“implementation”: “very_cool_fast_version”} as a single byte enum at the right offset in a struct. So binaries should be cheaper to deploy than configuration, in terms of size.

                                                                Fast rollbacks are another reason, etc.

                                                                Why shouldn’t I be able to quickly roll back to an old binary? That’s an ability that one desperately needs anyway. Or are you implicitly implying that my configuration will be dynamic and live-updatable while the system is running? That’s a whole different question and, as I’m sure you know, can be very tricky to get right. But if you support that ability then there’s no reason why your initial configuration has to come from a separate file instead of hardcoded in your binary.

                                                                1. 4

                                                                  I’d like to live in a world where all of this is feasible and can be easily budgeted for. Examples from my current work:

                                                                  use an interpreted language or rely on fast incremental builds.

                                                                  We have about 2.5 million lines of C++ that gets deployed to remote embedded-ish systems. Often changes need to happen in distant places over unreliable cellular internet. I’d love fast incremental builds; as it is an incremental build takes at least 1 minute on a rather beefy machine, and 10 on some of the machines we’d like to be using in the near future. We also can not put our source code on these remote machines, for IP reasons.

                                                                  Is your concern that admins/operators shouldn’t be writing code?

                                                                  Our operators/users are experts at operating high-tech machinery, not experts at coding. They have to worry about things like “make sure this machine doesn’t break itself or hurt anyone”; learning git is not very high on their priority list, and I’d frankly prefer to keep it that way.

                                                                  Do you mean purely in terms of size?

                                                                  A compressed binary deployment of our software is about 2.3 gigabytes. This is literally all code, libraries, and operational data. If we put the engineer-hours into cutting out all the stuff that changes rarely so we could deploy only changed code, setting up a new deploy pipeline to handle it, and training people to use it, we can probably get it down to 500 megs. (Hopefully we can get the time to do that sometime this year.) Again, remote updates usually have to happen via crap cellular internet. If rsync suddenly stopped existing the company would implode in two weeks.

                                                                  Why shouldn’t I be able to quickly roll back to an old binary? That’s an ability that one desperately needs anyway.

                                                                  And hopefully someday soon I’ll get the money to implement it!

                                                                  Or are you implicitly implying that my configuration will be dynamic and live-updatable while the system is running?

                                                                  We are desperately trying to make this not the case, but currently this is still necessary for field debugging and R&D. Having two packaging/deployment pipelines, one for production and one for development, would make it easier to handle this, and we are slooooooowly meandering towards that, but it takes a lot of up-front work and training.

                                                                  It’s been very interesting working here, and after bitching about it for two years I’m now a lead dev on the team responsible for actually finding solutions to these problems. It’s gonna be fun! But one of the things I’ve learned is how much of the world assumes “computer tech” equates to “a server living in a datacenter”. Make that not the case and suddenly a ton of existing systems and solutions stop working.

                                                        1. 4

                                                          The article’s point is valid, if a little platitudinous (yes, code reuse is good). But:

                                                          And they utilize universal interfaces: TCP, HTTP, etc.

                                                          Isn’t this utterly wrong? TCP and HTTP are nowhere near universal interfaces. Large amounts of the internet goes over UDP and QUIC, and there are many many different RPC frameworks out there.

                                                          TCP and HTTP might be common for moderate-performance high-latency moderate-reliability microservices, which is a large number of services. But if we want to solve the problem, we need something more universal than just the 70% solution. I don’t know how to achieve that level of universality (though I have some ideas) but it’s the goal we need to achieve. Though we’ll probably hit it accidentally - there were many operating systems before and after Unix which failed, and they were all trying to win - merely trying isn’t enough, we need to get lucky too.

                                                          1. 5

                                                            Hey, author here.

                                                            McIlroy’s quote specifically says:

                                                            Write programs to handle text streams, because that is a universal interface.

                                                            Emphasis on the “a” is mine.

                                                            There is no such thing as an absolute, truly universal interface. It’s a matter of degrees. Text streams certainly weren’t universal for the longest time! We had to standardize ASCII, and later unicode, etc. We’ve since standardized HTTP and it’s certainly more universal than something like a JVM method call.

                                                            1. 4

                                                              Isn’t this utterly wrong? TCP and HTTP are nowhere near universal interfaces. Large amounts of the internet goes over UDP and QUIC, and there are many many different RPC frameworks out there.

                                                              “Universal” is obviously not precisely defined, but if you’re gonna pick winners for OSI layers 4 and 7, I think the answers are pretty clearly TCP and HTTP respectively. Very little of the internet goes over UDP and QUIC at the moment.

                                                              1. 1

                                                                Large amounts of the internet goes over UDP and QUIC, and there are many many different RPC frameworks out there.

                                                                The quote is from someone’s post above. I didn’t know this to be true, I believe TCP/HTTP are still the majority of the internet “protocols” used today.

                                                              2. 3

                                                                I don’t agree it’s platitudinous because people nod their head “yes” when reading it, but when they sit down at their jobs, they code the area and not the perimeter :)


                                                                I see what’s meant by “universal”, but I also see that it’s a misleading/vague term. I would instead say that TCP and HTTP are the “lowest common denominator” or “narrow waist”, and that has important scaling properties.

                                                                A related line of research is “HTTP: An Evolvable Narrow Waist for a Future Internet” (2010).

                                                                https://www2.eecs.berkeley.edu/Pubs/TechRpts/2012/EECS-2012-5.pdf

                                                                QUIC and HTTP/2 seem to be pushing in that direction. Basically TCP/IP was explicitly designed the narrow waist of the Internet (I traced this to Kleinrock but I’m still in the middle of the research), but it’s moving toward HTTP.

                                                                As far as I understand, QUIC is more or less a fast parallel transport specifically for HTTP. While HTTP is now the thing that supports diverse applications. For example, e-mail, IRC, NNTP are subsumed by either HTTP gateways or simply web apps. As far as I can see, most mobile apps speak HTTP these days as opposed to TCP/IP with raw sockets.


                                                                Other names: “hourglass model”, “thin waist”, and “distinguished layer”:

                                                                https://cacm.acm.org/magazines/2019/7/237714-on-the-hourglass-model/fulltext

                                                                Basically this software architecture concept spans all of networking; compilers and languages; and (distributed) operating systems. But the terminology is somewhat scattered and not everyone is talking to each other.

                                                                But again, there’s something profound here that has big practical consequences; it’s not platitudinous at all.

                                                              1. 2

                                                                First idea that popped into my head is that this is only applicable if the thing in question is constructible. Non-existence is not constructible by its nature: I can prove to you that no decision procedure for the halting problem exists, but I cannot show this constructively.

                                                                1. 1

                                                                  I would really like to learn more about this, aren’t coq proofs constructive by nature?

                                                                  Here is a proof that the halting problem for turing machines is undecidable: https://github.com/uds-psl/coq-library-undecidability/blob/30d773c57f79e1c5868fd369cd87c9e194902dee/theories/TM/TM_undec.v#L8-L12

                                                                  There are other proofs in that repo that show other problems as being undecidable.

                                                                  1. 2

                                                                    Huh, TIL. Indeed, it seems that there exist proofs of the undecidability of the halting problem that are constructive.

                                                                    ¬ (A ∧ ¬ A) is apparently provable in intuitionistic logic, which suffices for diagonalization arguments:

                                                                    lemma "¬ (A ∧ ¬ A)"
                                                                    proof (rule notI)
                                                                      assume 1: "A ∧ ¬ A"
                                                                      from 1 have "A" by (rule conjE)
                                                                      from 1 have "¬ A" by (rule conjE)
                                                                      from `¬ A` `A` show False by (rule notE)
                                                                    qed
                                                                    

                                                                    The general point still stands though, as there are other examples, such as non-constructive existence proofs.


                                                                    In general Coq proofs are not necessarily constructive, since one can define the law of the excluded middle as an axiom in Coq (and this is done in e.g. Coq.Classical). I can’t say anything about the proofs you linked though, as my Coq-foo is very limited.

                                                                    1. 1

                                                                      I think, the way the definitions expand in that repo, that undecidable (HaltTM 1) is decidable (HaltTM 1) -> decidable (HaltTM 1), which is trivially true. That is, it’s taken as an axiom that Turing machines are undecidable. (I think? I might be misreading)

                                                                    2. 2

                                                                      while Coq proofs are constructive, proving “~ exists t, t determines halting” does not construct any Turing machines, what it constructs is something that takes a hypothetical existence proof and builds a proof of false from it.

                                                                      i.e. in construct math, ~ P is a function P -> False. this function can never be invoked as you can never build a P object.

                                                                    3. 1

                                                                      As this post was tagged practices (not math) and the article addresses the difficulty of convincing people, I suspect you’ve misunderstood the author’s use of the phrase “constructive proof.”

                                                                      Mathematical (constructive proof) vs rhetorical (constructive criticism).

                                                                      1. 1

                                                                        I don’t feel like the author talks about constructive criticism at all, how did you come to that conclusion?

                                                                        1. 1

                                                                          As this post was tagged practices (not math) and the article addresses the difficulty of convincing people

                                                                          ☝🏾

                                                                          1. 2

                                                                            Just for future reference, “constructive proof” is a term from math and I meant my usage of the term to be an analogy to math. Math also involves convincing people. But maybe I should have tagged it computer science or math, sorry.

                                                                    1. 4

                                                                      Lots of software practices follow a similar pattern, just over a much longer timescale. The pendulum swings eternal.

                                                                      1. 1

                                                                        Hardware has the wheel of reincarnation which is a similar concept. I submitted the original paper earlier this week.

                                                                        1. 1

                                                                          Nicely identified. I feel like abstraction inversion is another example (or perhaps a rewording?) of the swinging pendulum. X isn’t perfectly suited, so we build Y on top, and then Y isn’t perfectly suited, so we build X’ on top which rebuilds some of the features of X in a worse way…

                                                                          On the sort-of-plus side I think being aware of this and being willing to use old things/old design practices is kind of a superpower for software design… it can make things a lot simpler if you just follow the path that’s already been trod http://catern.com/reuse.html

                                                                        1. 5

                                                                          Yes and no, but more yes than no. My counterexample is that we had a big glut of NoSQL databases for a few years there in which everyone seemed to be providing constructive proofs of their ideas about distributed data storage, except that it took Aphyr’s tests to prove that many of those ideas didn’t really work, they only sort of appeared to work because they were never really tested under the conditions they claimed to support.

                                                                          This is one of the reasons I like math though. There are things you could never prove by constructing software, which you can nevertheless prove with math—the CAP theorem comes to mind. But a constructive proof in math is not exactly the same thing as a practical program you can run. (In theory, they might be.)

                                                                          1. 1

                                                                            Perhaps you could constructively prove the CAP theorem with a testing tool that can break any database.

                                                                            1. 1

                                                                              I have mixed feelings about this. On the one hand, such a tool would be useful and cool, and would certainly make it easier to stomp on some crank’s program. On the other hand, I feel like we’re sort of losing sight of what proof is, and replacing it with a kind of faith in a certain program.

                                                                          1. 2

                                                                            Schemas and signatures have different purposes. Schemas are much more useful when you expect people to need/want to implement their own version of the protocol to better fit their requirements/needs/projects.

                                                                            1. 1

                                                                              Could you elaborate? Why do you say that?

                                                                              1. 2

                                                                                Not OP, but I think @glacambre is saying that schemas are less specific than signatures, which grants implementors more freedom when implementing/maintaining one. Server-client architectures don’t even have an ABI, so we don’t need to worry about its compatibility. I can switch to another runtime/compiler or even another language whenever I want, as long as the protocol doesn’t change.

                                                                                Everything in an interface is a promise you shall keep indefinitely, so more expressiveness means more responsibility.

                                                                            1. 10

                                                                              I think an important direction for future programming language development is better support for writing single programs that span multiple nodes. It’s been done, e.g. erlang, but it would be nice to see more tight integration of network protocols into programming languages, or languages that can readily accommodate libraries that do this without a lot of fuss.

                                                                              There’s still some utility in IDLs like protobufs/capnproto in that realistically the whole world isn’t going to converge on one language any time soon, so having a nice way of describing an interface in a way that’s portable across languages is important for some use cases. But today we write a lot of plumbing code that we probably shouldn’t need to.

                                                                              1. 3

                                                                                I couldn’t agree more. Some sort of language feature or DSL or something would allow you to have your services architecture without paying quite so many of the costs for it.

                                                                                Type-checking cross-node calls, service fusion (i.e. co-locating services that communicate with each other on the same node to eliminate network traffic where possible), RPC inlining (at my company we have RPC calls that amount to just CPU work but they’re in different repos and different machines because they’re written by different teams; if the compiler had access to that information it could eliminate that boundary), something like a query planner for complex RPCs that decay to many other backend RPC calls (we pass object IDs between services but often many of them need the data about those same underlying objects so they all go out to the data access layer to look up the same objects). Some of that could be done by ops teams with implementation knowledge but in our case those implementations are changing all of the time so they’d be out of date by the time the ops team has time to figure out what’s going on under the hood. There’s a lot that a Sufficiently Smart Compiler(tm) can do given all of the information

                                                                                1. 3

                                                                                  There is also a view that it is a function of underlying OS (not a particular programming language) to seamlessly provide ‘resources’ (eg memory, CPU, scheduling) etc. across networked nodes.

                                                                                  This view is, sometimes, called Single Image OS (briefly discussed that angle in that thread as well )

                                                                                  Overall, I agree, of course, that creating safe, efficient and horizontally scalable programs – should much easier.

                                                                                  Hardware is going to continue to drive horizontal scalability capabilities (whether it is multiple cores, or multiple nodes, or multiple video/network cards)

                                                                                  1. 2

                                                                                    I was tempted to add some specifics about projects/ideas I thought were promising, but I’m kinda glad I didn’t, since everybody’s chimed with stuff they’re excited about and there’s a pretty wide range. Some of these I knew about others I didn’t, and this turned out to be way more interesting than if it had been about one thing!

                                                                                    1. 2

                                                                                      Yes, but: you need to avoid the mistakes of earlier attempts to do this, like CORBA, Java RMI, DistributedObjects, etc. A remote call is not the same as an in-process call, for all the reasons called out in the famous Fallacies Of Distributed Computing list. Earlier systems tried to shove that inconvenient truth under the rug, with the result that ugly things happened at runtime.

                                                                                      On the other hand, Erlang has of course been doing this well for a while.

                                                                                      I think we’re in better shape to deal with this now thanks all the recent work languages have been doing to provide async calls, Erlang-style channels, Actors, and better error handling through effect systems. (Shout out to Rust, Swift and Pony!)

                                                                                      1. 2

                                                                                        Yep! I’m encouraged by signs that we as a field have learned our lesson. See also: https://capnproto.org/rpc.html#distributed-objects

                                                                                        1. 1

                                                                                          Cap’nProto is already on my long list of stuff to get into…

                                                                                      2. 2

                                                                                        Great comment, yes, I completely agree.

                                                                                        This is linked from the article, but just in case you didn’t se it, http://catern.com/list_singledist.html lists a few attempts at exactly that. Including my own http://catern.com/caternetes.html

                                                                                        1. 2

                                                                                          This is what work like Spritely Goblins is hoping to push forward

                                                                                          1. 1

                                                                                            I think an important direction for future programming language development is better support for writing single programs that span multiple nodes.

                                                                                            Yes!

                                                                                            I think the model that has the most potential is something near to tuple spaces. That is, leaning in to the constraints, rather than trying to paper over them, or to prop up anachronistic models of computation.

                                                                                            1. 1

                                                                                              better support for writing single programs that span multiple nodes.

                                                                                              That’s one of the goals of Objective-S. Well, not really a specific goal, but more a result of the overall goal of generalising to components and connectors. And components can certainly be whole programs, and connectors can certainly be various forms of IPC.

                                                                                              Having support for node-spanning programs also illustrates the need to go beyond the current call/return focus in our programming languages. As long as the only linguistically supported way for two components to talk to each other is a procedure call, the only way to do IPC is transparent RPCs. And we all know how well that turned out.

                                                                                              1. 1

                                                                                                indeed! Stuff like https://www.unisonweb.org/ looks promising.

                                                                                              1. 12

                                                                                                This is a very useful thing to do, although magit calls it “instant fixup” (cF) - that’s how I do it.

                                                                                                1. 11

                                                                                                  Similarly I’m a big fan of git-revise for this and similar tasks.

                                                                                                  1. 9

                                                                                                    git-absorb too.

                                                                                                    1. 2

                                                                                                      git-revise with Zsh completions is awesome. You just press tab and choose the commit to revise from a nice menu. Things that would take three different git invocations and probably some head scratching are now just a couple of keypresses and really easy to get right every time.

                                                                                                  1. 1

                                                                                                    I found this interesting (along with a lot of the related posts and others on the site) but it felt like it was being presented as an alternative to something, but I had a hard time figuring out what that something was. DSLs seems vague. I think I finally got the idea from noticing the punny file name “caternetes.html”. The DSLs being referred to are at least in part things like k8s configurations or terraform etc.? Is that the idea? Analogously to the the post about writing code not configuration?

                                                                                                    1. 2

                                                                                                      Yes, that’s right. I kind of wanted to avoid being explicit about that, because I’m worried that if I call out any specific technology, fans of it will immediately dismiss this idea. :)

                                                                                                      But, your comment made me finally add a little (partial) list to the conclusion: http://catern.com/caternetes.html#conclusion where hopefully it will be less threatening.

                                                                                                    1. 4

                                                                                                      @catern:

                                                                                                      The alternative is writing ten programs and a thousand config files for configuration management, service discovery, orchestration, etc. I believe the advantages of the single-program approach are self-explanatory.

                                                                                                      🤔

                                                                                                      Turtles all the way down:

                                                                                                      After a lecture on cosmology and the structure of the solar system, James was accosted by a little old lady.

                                                                                                      “Your theory that the sun is the centre of the solar system, and the earth is a ball which rotates around it has a very convincing ring to it, Mr. James, but it’s wrong. I’ve got a better theory,” said the little old lady.

                                                                                                      “And what is that, madam?” inquired James politely.

                                                                                                      “That we live on a crust of earth which is on the back of a giant turtle.”

                                                                                                      Not wishing to demolish this absurd little theory by bringing to bear the masses of scientific evidence he had at his command, James decided to gently dissuade his opponent by making her see some of the inadequacies of her position.

                                                                                                      “If your theory is correct, madam,” he asked, “what does this turtle stand on?”

                                                                                                      “You’re a very clever man, Mr. James, and that’s a very good question,” replied the little old lady, “but I have an answer to it. And it’s this: The first turtle stands on the back of a second, far larger, turtle, who stands directly under him.”

                                                                                                      “But what does this second turtle stand on?” persisted James patiently.

                                                                                                      To this, the little old lady crowed triumphantly,

                                                                                                      “It’s no use, Mr. James—it’s turtles all the way down.”

                                                                                                      1. 3

                                                                                                        Ah, while I enjoy the quote for sure, I’m not sure how it’s related? What’s the infinite regress here? Seems like a single step: many programs to one program, and then you’re done.

                                                                                                      1. 7

                                                                                                        Hm interesting, but this architecture assumes that the whole system can be brought down and brought up atomically? (atomic upgrade)

                                                                                                        I mention that issue in my blog post yesterday: https://www.oilshell.org/blog/2021/07/blog-backlog-1.html#fallacies

                                                                                                        Fallacy: You Can Extend Your Type System Across the Network. Large distributed systems can’t be atomically upgraded. (Example: the entire Internet, although it’s also true at much smaller scales.) This conflicts with type safety as a global property of a program.

                                                                                                        And I go on to link this comment: https://old.reddit.com/r/ProgrammingLanguages/comments/nqm6rf/on_the_merits_of_low_hanging_fruit/h0cqvuy/

                                                                                                        Reading between the lines (with orderd), you’re working at a trading firm. I can see why they can take down their systems. One reason might be that some markets actually close; another reason it’s not a system with users outside the company. Or it could just be like my bank where their website goes down on Saturday nights :) (annoying)

                                                                                                        I’m thinking of stuff more like Google or the whole Internet – you can’t just take the whole thing down and restart it :) It’s up forever and has to be incrementally upgraded.

                                                                                                        So it would be nice to make this distinction in the article. I would say that only small distributed systems can be atomically upgraded. To be clear, this has a huge effect, and if you can take advantage of it, you should! I am not sure I can use it, but it’s interesting.


                                                                                                        FWIW I think the middle of the article was very lucid and clear, with a good example. However the intro was difficult to read and understand. I had to read it 2 or 3 times to figure out what you’re talking about. It’s a bit abstract, uses some vague non-standard terms (single conventional program, single-program system), and the style is a bit stilted. If you want to reach more people, I would suggest writing like you talk.

                                                                                                        I hope that is helpful; I say that because I noticed several people including myself had problems reading an earlier post: https://lobste.rs/s/glu4bw/write_code_not_compilers

                                                                                                        (And this is coming from somebody who has worked with all these things: Python, MyPy, C++, Unix processes, distributed systems. I’m used to reading jargon, but jargon can be put in simple / natural prose, which builds up crisp definitions, etc.)

                                                                                                        1. 4

                                                                                                          but this architecture assumes that the whole system can be brought down and brought up atomically? (atomic upgrade)

                                                                                                          No - there are many approaches here, like hot-code-reload of modules in the Python program, but one very simple approach is to just restart the main program and keep the processes running, and have the new version of the program resume monitoring all the child processes (through some Linux magic) and then start gradually killing and upgrading them.

                                                                                                          Edit: To paraphrase your broader point though, you’re saying that this only works for systems where one entity ultimately controls the entire system, so for example this wouldn’t work for the internet at large (but it would work for Google). Yes, that’s true. Two points on that: 1. I think most systems have one entity ultimately controlling them, and 2. This is a general pattern in many places, where there’s a larger “decentralized” environment containing interacting centrally-controlled individual entities: Economies contain firms, for example. I (probably) wouldn’t want to centrally plan the entire economy, but centrally planning a firm inside the economy is very beneficial - it’s Coase’s theory of the firm. For the same reasons, my system might interact with other systems I don’t control, but I’d still want to be able to centrally control my system - and it’s practical to do so.

                                                                                                          However the intro was difficult to read and understand.

                                                                                                          Thanks for the feedback, I’ll work on it, for sure.

                                                                                                          1. 1

                                                                                                            So are you actually doing hot reloading of Python code at your job? I would be surprised because that’s a very limited mechanism and not well defined from what I remember (e.g. reloading native extensions, etc.).

                                                                                                            I think what you’re describing is interesting, but you’re overstating the applicability of it, e.g. in the first sentence:

                                                                                                            one can write a distributed system as a single conventional program

                                                                                                            This is too grand a claim. That’s why it’s important to be concrete rather than abstract in the intro. If you say you work at a trading firm, that’s going to give me a very different picture than if you say you worked on “web services” (Facebook, google, etc.) or games.

                                                                                                            All 3 of those domains use distributed systems in Python and C++, but they have very different architectures, based on the different nature of the problems.

                                                                                                            Are you talking about stateless subprocesses/services with all state in the central Python process that’s upgraded? I can see how that can be a very useful and simple pattern, but there many other architectures of distributed systems. All of these have different implications for upgrade.

                                                                                                            • Stateless processes and a consistent database (you still have to deal with schema changes)
                                                                                                            • Stateless processes and a inconsistent distributed database
                                                                                                            • Ephemeral state in a single master, stateless or stateful workers
                                                                                                            • Stateful workers with immutable / append-only state
                                                                                                            • Stateful workers with mutable state (a hard one, lots of tradeoffs to make here)

                                                                                                            I’m about to link this survey in a sibling comment, so maybe that will give some color on why I’m talking about the upgrade problem:

                                                                                                            http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.5.9679&rep=rep1&type=pdf

                                                                                                            1. 3

                                                                                                              @washort and I cracked the problem of Python code reloading a decade ago; they created Exocet and I refined that into bravo.plugin. We used PEP 302 import hooks to customize how the Python interpreter imports modules. I used this in the Bravo Minecraft server to hot-load core functionality implemented as plugins. You’re entirely right that native extensions are not supported, and additionally Bravo configured Exocet to disallow plugins from importing threading, asyncore, etc.

                                                                                                              The code has rotted a little, and importlib is now standard, so a Python 3 version would be simpler and mostly concerned with the zope.interface API and topological sorting. Ultimately, though, this is part of why we created Monte; we needed better treatment of modules as immutable functions, or even just as plain old data objects.

                                                                                                              1. 1

                                                                                                                So are you actually doing hot reloading of Python code at your job?

                                                                                                                Not necessarily, I just mentioned that as an example of how there’s many, many approaches to this problem. The simplest approach (which I can’t really say whether I’m using) is the one I outlined after that:

                                                                                                                one very simple approach is to just restart the main program and keep the processes running, and have the new version of the program resume monitoring all the child processes (through some Linux magic) and then start gradually killing and upgrading them.

                                                                                                                That’s a general and simple approach which requires no fancy technology and can be extended to work for any distributed system architecture.

                                                                                                                Are you talking about stateless subprocesses/services with all state in the central Python process that’s upgraded?

                                                                                                                No, not at all. There’s lots of state in the individual nodes. This approach is not at all tied to any particular distributed system architecture. Any of those architectures you listed could be managed just fine.

                                                                                                                1. 4

                                                                                                                  OK well I’d like to read something about how you incrementally upgrade nodes in this architecture, where the state is, what the messaging formats are, which nodes message each other, etc. Without those details I’d still say you’re overstating the applicability of this pattern :)

                                                                                                            2. 4

                                                                                                              It’s fun to see that my slogan has come full circle; I started spreading this meme about type systems and networks years ago. Specifically, we cannot extend any value judgements across The Network. There’s two interlocking problems. First, we cannot enforce type judgements on serialized bytestrings. Second, we cannot assume control of Somebody Else’s Computer. This underlying logic is important because, for example, we cannot even enforce type judgements upon The Filesystem, since it has the same erasure to bytestrings!

                                                                                                              1. 4

                                                                                                                If you view the filesystem as a network where you send packets across time instead of space then it also has the Someone Else’s Computer problem. ;)

                                                                                                                I’m sort of only half joking about that.

                                                                                                                The past-you who wrote down those files is gone, so the future-you who receives them can only deal with what was sent, not what should have been sent. Future-you can’t update the types and have everything retroactively comply (in a useful way, at least).

                                                                                                                1. 2

                                                                                                                  Did you write something about this? Where?

                                                                                                                  This is a very old problem … It’s well known in certain circles but somehow not appreciated among current programmers – just like some of the other concepts in that blog post (e.g. policy vs. mechanism). Programming is a field with a short memory.

                                                                                                                  I experienced problems caused by distributed system upgrade by working on a variety of systems at Google for 10+ years.

                                                                                                                  I think around 2009 I read work on this from Sameer Ajmani, like this survey from 2002. The first sentence says the earliest work on distributed systems upgrade from 1983 (which sounds plausible to me; I imagine the distributed systems back then were small and unreliable by today’s standards):

                                                                                                                  http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.5.9679&rep=rep1&type=pdf

                                                                                                                  As far as I remember, he’s a prominent Go contributor; I was following the internal go-nuts@ mailing list before the initial release in ~2009, and probably saw his name there, and followed the links to his work. That’s not where I encountered the problems, but the framing is useful.

                                                                                                                  I’d be interested in what you said about it, if there is some unique take that’s not already been expressed, etc.

                                                                                                                  1. 2

                                                                                                                    I mostly synthesized the slogan, “type systems don’t work across The Network,” while I was deep into Haskell. It was around the time that I wrote gemstone in 2013, not long before I left Haskell altogether. This slogan was designed to refute several common claims which Haskellers often made about correctness of distributed systems. (To be clear, Haskellers were not ignorant of the problems with libraries like cereal, but had apologetics for minimizing the apparent severity of the issues. Memes are powerful.)

                                                                                                                    I was at Google immediately prior to that, though, and I recall being a Haskeller while at Google, so it’s quite possible that I adapted some Googler’s slogan or memes.

                                                                                                                  2. 1

                                                                                                                    This underlying logic is important because, for example, we cannot even enforce type judgements upon The Filesystem, since it has the same erasure to bytestrings!

                                                                                                                    Just serialize to structured objects in memory that persist to disk. Not a new solution.

                                                                                                                1. 7

                                                                                                                  So I’m sticking to the official PyPI distribution wherever possible. However, compared to the Debian distribution it feels immature. In my opinion, there should be compiled wheels for all packages available that need it, built and provided by PyPI. Currently, the wheels provided are the ones uploaded by the upstream maintainers. This is not enough, as they usually build wheels only for one platform. Sometimes they don’t upload wheels in the first place, relying on the users to compile during install.

                                                                                                                  For this and other reasons I prefer to use Nix as my Python toolchain. The packages are up to date, they all come with binaries, and they’re guaranteed to work no matter what distribution I’m running on top of. (Plus I don’t have to worry about the constantly changing “official” packaging workflow…)

                                                                                                                  1. 1

                                                                                                                    Adding onto this. I experimented with nix for this use as well. My problems are slightly different than most though. I need to ship python code (with C source dependencies) for an ARM SoC. I made a proof of concept that cross-compiled and ran on the target seamlessly.

                                                                                                                    It’s a bit convoluted exporting derivations to non-nix systems, however.

                                                                                                                  1. 11

                                                                                                                    Nice project, it’s definitely something I’ve wanted before. That being said, as the article mentions, this is mostly present in the standard library as “singledispatch”. But “classes” adds mypy support, which is really useful. But, it looks like mypy will be adding a plugin for singledispatch this summer: https://github.com/mypyc/mypyc/issues/802 so maybe the two libraries can merge together: https://github.com/dry-python/classes/issues/242

                                                                                                                    Also, one thing to remember is that you can always just “manually” encode the typeclass, by passing around an object like “Greeter[T]” whose methods support calling “greet” on T. So then to call greet on a str, you’d need a “Greeter[str]” object in addition to a “str”. This can work really well in many situations; for example, I use it here: https://github.com/catern/rsyscall/blob/master/python/rsyscall/struct.py#L10

                                                                                                                    1. 13

                                                                                                                      Easily one of the best blog entries I’ve ever read and the title is perfect like a mnemonic, it makes it easy to remember the principle. Although the code examples are in Haskell the content is so well written that it becomes approachable no matter what your background is.

                                                                                                                      1. 5

                                                                                                                        Is this fundamentally the same idea as “make invalid states unrepresentable”?

                                                                                                                        i.e. don’t have a corner case of your type where you could have a value which isn’t valid (like the empty list), instead tighten your type so that every possible value of the type is valid.

                                                                                                                        Looked at this way, any time you have a gap (where your type is too loose and allows invalid states) the consumer of the value of that types needs to check them again. This burden is felt everywhere the type is used.

                                                                                                                        If you do the check to ensure you haven’t fallen into the gap, take the opportunity to tighten the type to get rid of the gap. i.e. make the invalid state unrepresentable.

                                                                                                                        All sounds good - but I wonder how practical this is in practice? In a mainstream language, this kind of work is often done in constructors. If I have a ‘User’ class with a ‘name’ string, and my User constructor requires a non-empty name, the rest of the code in the user class can assume it is non-empty (if the only way of making a User is via the ctor)

                                                                                                                        Is that the same thing as what we’re doing here, or is there a material difference?

                                                                                                                        1. 4

                                                                                                                          I think it is related to “make invalid states unrepresentable”, but goes further to explain how to use types to achieve this. The crucial part is to understand the difference between validation and parsing as explained in the article. Applying it to your example validating a username to be non-empty is fine as long as the Constructor is the only way to construct that object, but parsing it would mean narrowing it to a different type, say a NonEmptyString. Or perhaps a Username type now makes sense. Passing that type around means you don’t need to re-validate your assumptions because now indeed you have made invalid states unrepresentable.

                                                                                                                          1. 4

                                                                                                                            parsing it would mean narrowing it to a different type, say a NonEmptyString. Or perhaps a Username type now makes sense. Passing that type around means you don’t need to re-validate your assumptions because now indeed you have made invalid states unrepresentable.

                                                                                                                            As far as I understand, it is not practical with Python to apply that advice. Any ideas how to do it?

                                                                                                                            1. 3

                                                                                                                              Throw an exception in the constructor of Username if the passed-in string doesn’t meet the restrictions for a username.

                                                                                                                              1. 2

                                                                                                                                I was thinking today that it should be perfectly possible to apply the “parse, don’t validate” approach in Go, and I think it is a viable thing to do in Python too.

                                                                                                                                If I understand correctly, the blog post is advocating to parse a logical representation of your domain model out of the primitive data that you get from other systems (strings, lists, json, etc). There is a benefit in pulling all that in a separate step that is independent of the real logic. This gets you several benefits:

                                                                                                                                • Fewer bugs, as you do not work with primitive objects that can have different meanings and representations.
                                                                                                                                • A central place to handle all your external data transformations that is easy to reason about.
                                                                                                                                • No half-complete operations. Say a 10-write process completes 5 writes, and the 6-th before-write validation fails - how do you handle that?

                                                                                                                                Doing all that in a language like Haskell that has an advanced type system is great, but I think we can do it in other languages even if we don’t have that 100% guarantee that the compiler is watching our back. For example, a parsed OO structure in a dynamic language composed of classes with a custom, domain-specific API is a lot better than the usual lists-in-dicts-in-more-lists-and-tons-of-strings spaghetti.

                                                                                                                                1. 1

                                                                                                                                  I don’t have any experience with doing that in Python. You can technically subclass str, but failing in a constructor is not so nice and creating a new method to parse a string opens the door to construct the object with an invalid value.

                                                                                                                          1. 7

                                                                                                                            This is a little off-topic, but about the original tweet:

                                                                                                                            I wonder if the reason that “don’t plan, don’t abstract, don’t engineer for the future” is such good advice is that most people are already building on top of highly-abstracted and featureful platforms, which don’t need to be abstracted further? So 1. if you need some abstractions in the future, you’ll already have some decent ones even if you don’t make your own, and 2. if you do make your own abstractions, that would likely result in abstraction inversions.

                                                                                                                            For example, contrast A: building your own system for high-performance parallel execution optimized for future usecases with B: just using a bash pipeline that supports what you need now. Most would advise doing B, at least at first; but B is only an option because you’re running on an already highly-abstracted platform with many existing features.

                                                                                                                            1. 5

                                                                                                                              I wonder if the reason that “don’t plan, don’t abstract, don’t engineer for the future” is such good advice is that most people are already building on top of highly-abstracted and featureful platforms

                                                                                                                              I think the reason simpler: People are good at understanding concretions and what they have experienced, and bad at keeping many things in their head.

                                                                                                                              A non-abstracted solution is a solution for a particular problem that is well-understood and usually maps directly to the conversations everyone at the company is already having. An abstracted one posits imaginary use-cases. This creates a doubly whammy: you don’t have concrete experience with those use-cases, and now, to understand the motivation of the code’s design, you must keep extra use-cases in your head.

                                                                                                                              1. 1

                                                                                                                                I definitely think this is one of the reasons that the original tweet is good advice. It’s tempting to conclude that an existing API is bad, for example, and that you’re going to improve it by wrapping it–but this is actually quite the feat to pull off IMO!

                                                                                                                                I’ve definitely been guilty of this, and time and time again I find out that even if the API had problems, I did not correctly identify the constraints up front that the folks designing the API had already become aware of. This either results in the wrapper taking a long time to design, it being of low quality/usefulness, or both.

                                                                                                                                That’s not to say that it can’t or shouldn’t be done–just that you should know you’re taking on a serious project when you decide to do this, and you probably want to have some real world experience with the API first.

                                                                                                                                Another reason the tweet is good advice is that engineering for the future necessitates predicting the future, and that’s a very hard thing to do. When you’ve just started working on a problem is when you know the least about it, so you’re in the worst possible position to make long term plans.