The only big things here are the fact that this is defined as a function that takes the package repository as an argument to access dependencies (the first line), and the "with" expressions that allow me to skip having to type "pkgs." and "pkgs.python3Packages." for every package I depend on.
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.
By the way, there is a list of dependencies, but no version numbers. Is that ok? Even if in theory packages should be backward-compatible, it doesn't work in practice.
Regarding syntax, I would prefer something like this:
This works fine in Nix because those package names are the names of derivations that themselves pin a given version of said packages.
In practice, the default set is expected to be somewhat internally consistent, so that you can just use the package names to refer to the "most recent consistent set". But if need arises, you can also override individual packages to pin them at a different version than the upstream-provided one.