Consfigurator 1.0: Common Lisp based declarative configuration management system
spwhitton.name
spwhitton.name
"Declarative configuration management systems like Consfigurator and Propellor share a number of goals with projects like the GNU Guix System and NixOS. However, tools like Consfigurator and Propellor try to layer the power of declarative and reproducible configuration semantics on top of traditional, battle-tested UNIX system administration infrastructure like distro package managers, package archives and daemon configuration mechanisms, rather than seeking to replace any of those. Let’s get as much as we can out of all that existing distro policy-compliant work!"
One timecard system we ran deployed a WAR in JBoss, but the associated DB schema upgrades were hand-compiled into a Java GUI with literally no externally-scriptable control surface.
It would be good to see more lisp/prolog solutions to this. It's super clunky the way terraform hcl added loops, or aws cloud formation added variables to javascript. What I really need is an abstract syntax tree that has code and data mixed, that can have different ways of reasoning or operating on that same information. Definitely lisp seems like a better fit, but who knows if it could ever be made as easy to use.
* Installation: https://spwhitton.name/doc/consfigurator/installation.html
* Sample code: https://spwhitton.name/doc/consfigurator/tutorial/disk_image...
When someone says "In Lisp, everything is a list" ... They really mean everything
More seriously. :)
The parens are definitely necessary. Think of it as sort of indentation too? Taking a typical factorial and just making up some kind of language which just uses indentation and infix notation, I mean, they're pretty similar? But the nice thing about lisp is that zerop, -, factorial are all equivalent and in the same place in the expressions. You could remove all the parenthesis and just use indentation. If you want to introduce strong typing, you can do that too.
(defun factorial (x)
(if (zerop x)
1
(* x (factorial (- x 1)))))
def factorial (x)
if zero? x
then 1
else x * factorial(x - 1)
Mentally, the parenthesis go away when you get used to them, and that happens pretty quick because there isn't any syntax to worry about. In many ways, Lisp is the simplest possible language that can do useful things. It's a thin layer on top of lambda calculus, and the early versions of lisp were more like machine language (CAR, CDR are left over from this history) but it's survived so long with almost no changes because it's pretty much the ur-language, spanning high level and low level abstractions. I expect almost any useful feature from another language could be implemented in lisp.One benefit is that code == data. It lends itself to all kinds of meta-programming fun, the REPL, etc. The parenthesis become invisible to the reader after a while and for me, the difficulty in understanding lisp code is not the parenthesis at all, but knowing the behavior of the different special forms and macros and functions available in common-lisp (read the documentation for LOOP sometime for a good time).
Oh, here's a good example of the complexities of LOOP from "Practical Common Lisp" https://gigamonkeys.com/book/loop-for-black-belts.html
As that chapter describes, LOOP is a DSL just for doing "looping things", of which there are a lot! But it's implemented as a macro, not a special keyword that can only do one boring thing.
(loop for i in *random*
counting (evenp i) into evens
counting (oddp i) into odds
summing i into total
maximizing i into max
minimizing i into min
finally (return (list min max total evens odds)))The Python one, for example, looks like this: https://docs.python.org/3/library/ast.html
It’s pretty complicated to walk a Python AST — lots of new concepts to learn, not very intuitive compared to the language, and even harder to use if you want to modify the tree. The Python one is one of the nicer examples!
In a lisp like language, the language syntax itself is the same as the data structure syntax. Because these two universes align, you can do clever things with the language because it is as easy to thing in terms of the language syntax tree as it is to write code in the language itself. It makes it trivial to create new domain-specific-languages / meta-languages that make it faster to write code to solve a particular class of problems.
There are two downsides though. First, it is cumbersome to get used to writing you program in a data structure. Certainly not as cumbersome as, say, writing it in XML (like the language XSLT does) but still pretty cumbersome.
Secondly, the allure of meta programming means that it’s very easy to create a code base that isn’t intuitive to another programmer, even if you both have a lot in common already. You might define a handy log function that you use a lot called “chip”. The other person had the same good idea for the same good abstraction and they called it “twig”. You get used to writing chip a lot. They do the same with twig. It feels weird to use each other’s paradigms, even though you were both correct in abstracting out this clever logging thing that was lacking in the standard library.
Lisp ecosystems suffer from this a lot. Moving between codebases is like moving companies, each with a time consuming onboarding process relating to the house style.
Contrast this with Python where there aren’t that many Python implementations that have wildly divergent standard libraries. (MicroPython springs to mind as the only example, off the top of my head, as something that looks like standard Python but is missing some key things.)
It means that I can easily read and understand what's going on in a program I've never seen before, or in my own programs after I've been away from them for a while. This is doubly important for me, as I jump between programming languages a lot.
In other, more terse languages, getting back up to speed is a lot harder for me, as they use a lot of shorthand that I forget after being away from them for a while, but with Lisp spelling things out by default, reading it is a lot easier for me.
This increased readability also makes for much easier management of complexity. I can build on a readable foundation to make a more readable larger program as well.
At the opposite side of verbosity you find the terseness of regular expressions and conventions favoring the one or two character variable names and operators of languages like Haskell (not to mention various esoteric languages deliberately designed to be difficult to program in). I can understand the appeal of this sort of compression, and sometimes it's useful for little programs, but for me they tend to lead to a write-only style that's too high a price to pay unless I'm doing nothing but programming in that one terse language constantly so I never forget what all that line noise means.
I generally value simplicity, clarity and spelling out exactly what one means over terseness and cleverness, and that's a big reason why I prefer Lisp (and especially Scheme, which is the simplest and most elegant Lisp dialect that I know of).
As for parenthesis, they make it a lot easier to reason about what's going on in the program. Instead of the language forcing the programmer to do the work of figuring out what expressions apply to what, in Lisp it's clearly spelled out for you so you know exactly what's going on by the parenthesis. It just makes programs a lot clearer, and it also makes the implementation of other language and editor features easier, as it removes a lot of ambiguity in the code.