If you think the price is high, I would point out that the SSD I used cost €200 new when I purchased it back in mid 2022. A used 120 GB SSD by contrast can be had for maybe €10 which alone would explain the difference in cost.
Now if 120 GB is enough for your application, that's a good value so more power to you.
If this was going to be used for some "big and serious" application, maybe different choices would have been made. Hopefully it was clear from the post that my goals here were the exact opposite!
In my own anecdotal experience of running a hobby server on Arch for several years, I haven't experienced anything to make me think the distro is unsuitable for server work.
I've used debian for almost twenty years and arch for over half that, despite being comfortable with Arch, I would not sleep well at night if anything mission critical depended on it. You install a rig with Debian on it and touch nothing, it will last longer than you.
No, it will not last longer than you. If you want security updates, you have to do major ditro upgrades when the support for previous Debian release is over. And these are somewhat tricky.
You'll just have your upgrade dance clustered into a single event once every N years, instead of spread over randomly, based on major releases of SW your server depends on.
Yes, I considered that and agree that it would have been nicer! I didn't pursue it for this project because my jury-rigged SD boot was working fine and I wanted to move on to other parts of the system.
SD cards are pretty unreliable so it might be good to take it out of the loop at some point. Most field failures I’ve seen for products I develop have been related to SD cards dying during power loss or falling out due to vibration.
It rarely makes sense. Pointer to const is a contact between caller and callee. A signature like char <star>strdup(const char <star>) says "I take a pointer to memory that I promise not to modify, and you get a pointer to memory that you may modify".
Const pointer is a statement about the internal variables of a function definition, usually not of any interest outside the function itself and therefore rarely used.
> Const pointer is a statement about the internal variables of a function definition, usually not of any interest outside the function itself and therefore rarely used.
...and in fact not even part of the name mangling (for the exact reason you mentioned): https://godbolt.org/z/1pjecq
I'm not really sure what you're finding inscrutable. I'll tear out the boilerplate that explains that this file is a function and returns a Python application (which is documented in the nixpkgs manual and can just be copy-pasted if you really don't want to figure out what it does):
Is that any better? It looks an awful lot like some dialect of something JSON-like to me - there's some different symbols used and array values are separated with spaces rather than commas, but aside from that it's just a record of key-value pairs.
From there, the first line in the original example says "the following expression is a function that is called with this parameter", and the parameter is an object containing a key called "pkgs", which is defaulted to the result of "import <nixpkgs> {}". This is explained in more detail in a handful of lines in the language manual.
Functions are called just by referencing the function and then the arguments separated with spaces, like Ruby or Haskell - so python3Packages.buildPythonApplication is a function that is called with the object above.
Finally, "with foo;" just means "every key in foo can be accessed without typing foo. in the next expression".
The result is basically: this file defines a function which takes an object containing the package repository as the "pkgs" key, and returns the result of calling pkgs.python3Packages.buildPythonApplication with the object defined in the rest of the file.
It's a lot more derived from the syntax of functional languages than C-like ones, but that's not necessarily a bad thing. Also: this is basically the entire language. There's a couple of different string syntaxes and some stuff for merging objects together, and also a mechanism for temporarily naming things that aren't part of an object, but in general... there's not a lot more to it. Most of the complexity is in the domain itself - what's the difference between a propagated, native and regular build input, how do you manage plugins and options for a package, etc.
I mostly deal with openembedded at work and I have few positive things to say about it. I'm sure Nix is much faster and nicer. Frankly it's hard to imagine that the opposite could be true.
I'm not sure what to say. Replace the = signs with colons, the semicolons with commas, stick commas between array elements and stick everything between quotes and you basically have json.
The complicated bits are the import notations (what do the different variations mean and when to use them?), the recursive declarations (and knowing when to use them). Also, since expressions are often functions and the nixpkgs people don’t believe in types or docs, it’s difficult to track down the type info for each argument (what properties does it have?). You just end up grepping blindly through nixpkgs to find the callsite and even then the argument is often the result of a function call, so you have to find the function and it’s definition and look at the return and hope that it is not “return someOtherFunc()” lest you have to track down yet another function (also, odds are “someOtherFunc” is not the name of the function, but rather the name of a parameter into which a function was passed, so you’re back to blindly grepping nixpkgs).
Nix is great in concept but the language or idioms make it really difficult to use.
The example lacks functions / flow-control statements (ok, apart from "import if not defined", but it's more of a default value than a branch) / references to other elements. It's pretty much key-value dictionary. Not sure how you got an impression of Turing-complete DSL from that, since it would require at least one of them.
The Nix expression language has branches and functions (including recursive functions). It is Turing complete, and it probably is necessary for its charter.
OP said it "looks like" a Turing complete DSL just from the example. Regardless of what extra functionality the actually is, the examples didn't suggest it.
OOP contains in-place mutable state because mutating state is extremely important in basically all computer systems.
FP has become fashionable in recent years and now it's gone to people's heads. But some difficult facts about computer science remain:
- CPUs are much faster at reading and writing to recently used locations. Mutating in place is fast compared to constantly copying things.
- Many of the most efficient data structures and algorithms require mutable state. There are entire areas of computer science where you cannot implement them efficiently without mutable state, like hash tables.
- Constantly copying immutable objects places enormous load on the GC. This is getting better with time (there are open source ultra-low-pause GCs now), but, Rich Hickey just sort of blows this off with a comment that the "GC will clean up the no longer referenced past". Sure it will: at a price.
- He repeats the whole canard about FP being the only way to exploit parallel programming. People have been claiming this for decades and it's not true. The biggest parallel computations on the planet not so long ago were MapReduce jobs at Google: written in C++. Yes, the over-arching framework was (vaguely) FP inspired. But no actual FP languages appeared anywhere, the content of the maps and reductions were fully imperative and the MapReduce API was itself OO C++!
Also note that Java has a rather advanced parallel streams and fork/join framework for doing data parallel computation. I never once saw it used in many years of reading Java. SIMD is a much more useful technique but nearly all SIMD code is written in C++, or the result of Java auto-vectorisation. FP programming, again, doesn't have any edge here.