Building with Nix on Replit
docs.replit.com
docs.replit.com
Users vary from teachers building environments for their classrooms, to hackers tinkering with emulators, to kids making old games like run online.
We think we’re just scratching the surface with what we can do with Nix. It’s an outrageously underrated technology.
Super agree and watching from afar. Would love to see a blog post from replit "why not docker" (which is likely to go into how docker is the wrong sort of encapsulation, caching, source/binary substitution?)
But many people (ab)use Docker to solve the kinds of problems that Nix would have been better suited to solve.
ps. to run nix on gitpod use https://github.com/gitpod-io/template-nix
pps. i'm doing a talk on the 6th on nix. Cya there? https://twitter.com/GeoffreyHuntley/status/14424751054271528...
Okay, I'm going to nitpick: Termux proves this isn't a requirement of apt, just most distros using it, and in any event fakeroot/proot would solve it if that was the goal. Of course, nix is a mostly good system with loads of other benefits; it's only that specific reason that feels weak.
It’s worth it though. There’s something magical about having an entire machine configured declaratively.
If you're only setting up one server in some cloud, then probably it's easier to just set that up through the admin dashboard compared to setting up Terraform. For such a limited usecase, the benefit of "infrastructure as code" is that at least what you've got is documented.
But with using Terraform, all sorts of use cases are then enabled beyond what you'd want to manage manually.
Using Nix instead of manually installing a bunch of stuff seems fine either way. But stuff like nix-shell paired with direnv seems pretty neat.
I use Terraform to manage my own personal AWS account. Recently I needed a Windows VM to run some random exe file. I could have created an EC2 instance adhoc through the web console but I did it through terraform instead. It probably took me about 30 minutes longer than it would have otherwise, but now I've got that terraform module ready to go if I ever need a windows instance again in the future. It's got a properly configured security group that only allows access from my VPN, and it outputs the IP and admin password automatically. I'll never have to solve that problem again (well, for a few years if we're being realistic).
I absolutely believe you (Nix has a bit of a learning curve), but just as a counterpoint I'd like to mention that I've been using Nix with all sorts of languages (compiled and interpreted), for various projects (local applications, cloud services, static HTML sites, etc.), on Ubuntu, NixOS and macOS.
I recently had to interact with Docker for the first time and found it very bizzare (annoyingly, lots of documentation (e.g. that of AWS ECS) assumes its readers are already familiar with Docker!). It took many attempts to install it, using various different guides, versions, etc., many of which turned out to be obsolete (e.g. "docker machine" was recommended, but that's apparently replaced by "docker desktop", but now I'm hearing I should use "docker compose"?).
After I eventually figured out how Docker works, I ditched it completely since I found it easier and more reliable to build container images using Nix and run them directly.
nixpkgs.yarn2nix-moretea.mkYarnModules {
name = "my-project-deps";
pname = "my-project-deps";
version = "1.0";
packageJSON = ../package.json;
yarnLock = ../yarn.lock;
}
I copy things around, invoke commands, etc. using `nixpkgs.runCommand`, which runs shell code (a lot like rules in Make).I'm not sure about postgresql, since I've not used it before:
- You could use Nix to install the packages, and manage it yourself. That wouldn't be particularly different from non-Nix setups.
- You could install it on NixOS by specifying these options in your config https://github.com/NixOS/nixpkgs/blob/release-21.05/nixos/mo... (they're also listed at https://nixos.org/manual/nixos/stable/options but that page is slow). Of course, that requires NixOS (or a VM).
- You could run it in a container/VM, e.g. like https://nix.dev/tutorials/integration-testing-using-virtual-... but that seems a bit more advanced.
{ pkgs }: { deps = [ pkgs.cowsay ]; }
It’s meaningless to my brain.
{ pkgs }: { deps = [ pkgs.cowsay ]; }
({ pkgs }): { deps = [ pkgs.cowsay ]; }
({ pkgs }) => { deps = [ pkgs.cowsay ]; }
({ pkgs }) => ({ deps = [ pkgs.cowsay ]; })> ({ pkgs }) => ({ deps = [ pkgs.cowsay ]; })
But the syntax for declaring objects in javascript differs. It would be:
({ pkgs }) => ({ deps: pkgs.cowsay })
The fact that '{ x = 1; }' is declaring an object, identical to '{x: 1}' in javascript, is important.It's also worth pointing out that, while '{ pkgs }:' is idiomatic nix, idiomatically in javascript you'd probably just take multiple arguments (so '(pkgs) =>', not '({ pkgs }) =>' in js). The reason nix uses an object there is because a nix function only takes one argument, so there's a fairly common convention to make that argument an attr in order to simulate multiple arguments. That convention doesn't exist in quite the same way in js.
I also translate all nix to JavaScript in my brain
({ pkgs }) => { deps: [ pkgs.cowsay ] }
or python:
lambda pkgs: { deps: [pkgs.cowsay] }
I guess really the question is why create a new language rather than using an existing one? The language needed to be functional, lazy, and good at managing packages, and not much else. I guess they felt that no existing language fits the bill (Haskell does I guess but it's got a lot of baggage). I can't claim to know much about its history.
I had tried to compile some software that linked against macOS SDKs, and did not realise that Nix essentially maintains it’s own version, and it was quite tricky to adapt to it or get back to using Xcode’s toolchain.
I think following simple examples is straightforward. I think anyone who can install python and ruby with brew would be able to look at a replit.nix which uses python, and figure out how to change it to also install ruby.
Even for just simple cases, I think Nix is compelling and worth looking into.
"Nix is weird" becomes more of an issue for a long tail of edge cases, where a developer is likely to bump into one of these, and it won't be clear where in the documentation to go. -- With Nix being so weird, it both requires a familiarity with what you're doing in a standard-linux way, as well as familiarity with how things need to be done with Nix.
e.g. the nginxModified in the OP makes use of overrideAttrs. (You'd need to know both that nginx can be configured like that, as well as how to configure with nix).
* it explains in plain English what Nix is.
* it provides additional resources
* it has hands-on, guided, deliverable, tutorial.
> Functional languages are composed of functions that take some input and produce some output. Every time a function is executed with a given input, it will return the same output.
Surely that is a property of pure languages, not functional ones. Maybe there are no pure imperative languages but I don't see why you couldn't have one. In fact it would probably make way more sense for Nix given how much easier imperative languages are to understand.
Something more useful is an imperative language which only uses immutable values (strings, lists and so on).
I think this is not true. If it was this way, there would be no way to read data from an API. read('https://news.ycombinator.com') will not return the same output everytime.
And it would not be possible to keep track of anything. No counting, no transformation of data, nothing.
Googling a bit indicates that people use a mental trick and define the state part of the input of the function. So in terms of getUrl() the current state of the internet is part of the input of the function. And when counting something, the function gets the old sum as the input and outputs the new sum which gets stored in a different place than the old sum so the old sum "did not change".
I see a lot of overhead coming from these mental gymnastics with no benefit. Any short example that shows a benefit?
"Yeah, the function did not produce a different output. It just behaved different by throwing an exception."
Still, some other code will read (or "interpret") this behavior and then behave accordingly etc. So what is won here?
The function didn't return a value, so its caller cannot interpret that value and cannot change it behaviour.
Why do you think the only way to read from an API is with a function call? Have you not heard of monads?
> Functional languages are composed of functions that take some input and produce some output. Every time a function is executed with a given input, it will return the same output.
It does not mention "monads".
> And it would not be possible to keep track of anything. No counting, no transformation of data, nothing.
All of this is possible without state. Counting could be done with, say, 'builtins.length'. As for transforming data, that's what programming languages literally do. The Nix language isn't an exception here. In fact, the Nix language has everything to do with transforming data. It transforms high level package definitions to lower level build instructions.
> I see a lot of overhead coming from these mental gymnastics with no benefit
At least with the Nix language, there's no mental gymnastics going on. Defining a package is as simple as it gets, as can be seen here[1]. The benefits are reproducible package builds and composability. Since Nix functions don't contain state, it's really easy to combine abstractions and make modifications to existing packages compared to conventional package managers. See the Chrome example in the NixOS Wiki[2].
[1]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applicatio...
Things like network requests, or reading from files, or keeping track of state like a counter, are effectful. So, yes, a function which does these things couldn't be pure.
It's still nice to be able to have a pure function over an impure function. (Sortof the same "nice" as in "it's nice to not use global variables for everything in your code"). An impure function might be more difficult to think about than a pure one.
In languages like Haskell, all the functions are pure. But useful programs are effectful. There are different ways Haskell can still have useful programs while only allowing pure functions. (The most common way this gets done is using monads). -- One trick is "algebraic effects", this blogpost is an accessible intro: https://overreacted.io/algebraic-effects-for-the-rest-of-us/
Functional languages themselves are about a bit more than pure functions. It's about being able to pass functions around.. languages like JavaScript or Python have functions like map/filter/flatMap. Functions which take functions as parameters. They're a neat way of writing code.
The JSON program JQ's language is comparable to Nix. -- You could write your JSON-processing programs with Python (or whatever), or you can use some JQ expressions to do the processing.
There are two ways functional languages get around this:
Most functional language don't force functions to be pure. So, while they tend to be composed of functions that produce the same output for a given input, they aren't entirely. That is, they're just like any other language in this respect. Nonetheless, functional programs tend to separate the impure parts using patterns such as https://www.destroyallsoftware.com/screencasts/catalog/funct...
Then there are the languages where functions actually are necessarily pure - mostly Haskell. But this just means that the imperative actions aren't functions, not that they don't exist. The big impact is that, since a pure function can't invoke an impure action - or it wouldn't be pure - you have to use function-code-imperative-shell or your program won't compile.
I'm personally getting more enjoyment learning this style of environment and OS management with a more established language for the configuration.