Live data from Hacker News

A Lisp adventure on the calm waters of the dead C (2021)

mihaiolteanu.me

21–30 of 33 posts

Re: A Lisp adventure on the calm waters of the dead C (2021)

#21

I see mixed comments, so let me add some praise. I am one of countless, who match his intro-filter: repeatedly hearing 'enlightened' people lament that the vast masses don't "get" lisp and FP, and repeatedly attempting/failing to pick up the red string myself. background - I am a computer science major with 30+ years experience. I did do a mandatory class of 'implement your own lisp' many eons ago. It just never real…

Lisp is "functional" in a 1970s sense in that it has functions as first-class objects you can pass as parameters to other functions, but those functions are basically subroutines which can have side-effects and un-functional behavior. As you allude to, this is pretty much par for the course in procedural languages now, and OO As She Is Spoke is procedural with some extra stuff added. Even garbage collection is common enough now that languages which don't have it trumpet the fact and make it their whole personality. Lisp is heady and revolutionary if your baseline is FORTRAN and maybe C, in other words, unless you actually do begin to write your own macros, at which point the C people begin to look at you funny.

Haskell is functional in that it demands its functions be functions, not subroutines. A function has inputs mapped to outputs and no side-effects. Functions can be composed and composition always works. Haskell uses monads to represent the regrettable fact that having an impact on the outside world is, in a very real sense, a side-effect, so it marks all side-effecting functions with an indelible stain. Haskell requires a different mode of thought from Python, or even from C++, and it's definitely not another Lisp.

Re: A Lisp adventure on the calm waters of the dead C (2021)

#22

    typedef int fn_t(int, int);

    int iff(bool cond, fn_t a, fn_t b) {
        if (cond)
            return(a());
        else
            return(b());
    }
Now just write the implementation in terms of a() and b(). I don't get it. C doesn't have convenient syntax but this is compiled and not an evaluated language. This argument didn't make sense to me.

Re: A Lisp adventure on the calm waters of the dead C (2021)

#23
The author of this blog is clearly eloquent and, as per their interspersed quotations of David Hume and others, it is refreshing to see someone so well-read in the software/tech blogosphere.

I love Lisp. The last few paragraphs are a pretty good description. It's nice to have a very flexible set of tools, instead of being forced to conform to object-oriented design or whatever paradigm. IMO the only legitimate reason in sticking steadfast to a design paradigm is for performance reasons, but of course this can only really justify array programming/imperative programming. But at the point where you want some flexible abstractions, it's nice to have the power to do introspection, delayed evaluation, and so on. Disclaimer: my background is physics/math, so function abstractions are much more intuitive to me than objects, or whatever other structures are taught to CS students.

Re: A Lisp adventure on the calm waters of the dead C (2021)

#24

I see mixed comments, so let me add some praise. I am one of countless, who match his intro-filter: repeatedly hearing 'enlightened' people lament that the vast masses don't "get" lisp and FP, and repeatedly attempting/failing to pick up the red string myself. background - I am a computer science major with 30+ years experience. I did do a mandatory class of 'implement your own lisp' many eons ago. It just never real…

my epiphany with lisp was that it is not a functional language in the modern sense, i.e. mutability is fine, loops are fine, etc. it's primarily a list processor, not lambda calculus.

Re: A Lisp adventure on the calm waters of the dead C (2021)

#25
post #10

Earlier quoted context omitted.

I don't think you're missing much. Yeah, the main point seems to be that if your language has closures, you suddenly can express a lot of things that were out of reach before. Not a new insight. But there's another point I think that is hinted at on the topic of control abstractions. Or at least I'm reminded of the topic. It's better and more succinctly and explicitly talked about in an early chapter of the free book…

There's an upside to C's limitations. Several times I've seen expressed the notion that "every big Lisp project uses its own dialect of Lisp". This is less true in C, I think, because it doesn't have the power (ignoring preprocessor abuse, which does happen, see Bourne shell source[1]). C++ has a bit more power, and there we see projects tend to use their own subset of C++. In both Lisp, and C++, taking some isolated…

It's sometimes expressed and repeated but I think usually by people who don't do much or any Lisp programming. Like they hear Lisp is great for making DSLs and interpret that as every interesting program must have created its own wacky DSL and is basically incomprehensible outside of the program. That's not the case. At least in Common Lisp. Similarly I don't tend to see much (any?) "CL is too expressive and big and complex, thus we define a language subset and if you contribute/work here you must use said subset" attitude which is more prevalent in C++. Certain people and companies have their own stylistic preferences, sure. I could believe there are some out there that go all the way to requiring fset/immutable collections everywhere (Clojure-esque without switching languages to Clojure), or outright banning CLOS or lists or using conditions or ever writing your own defmacro or something (to try and put an analog on "no virtual methods, no linked lists, no exceptions, no preprocessor beyond #include" that I've seen for C++ guides). But none come to mind.

What did just come to mind is Coalton, a project I like though haven't tried using seriously yet, which more says: stock CL is too inexpressive for programs we want to write that make use of static algebraic data types with more compile-time guarantees. So here's a macro wrapped in a library that's effectively a new language, but we didn't have to go build a whole new language, we could just build on top of CL, and we kept the syntax tasteful and familiarly lispy. The interop is great. If you see a project using coalton and you want to use their code in CL, you can. And vice versa, using CL from coalton, it's just a library. And it's transparently done, obvious what is happening / what you're doing. CL allows this flexibility. Most projects do not birth a new language to support something.

Pick a random file from some larger projects that do come to mind: Kandria (commercial game), SHOP3 (a Hierarchical Task Network planner), Mezzano (an OS), Open Genera (an ancient OS), Maxima (computer algebra software that still uses code from the 80s), and it's pretty much all just... normal Lisp code. There are style differences, sure, but it's normal, easy to understand what it's doing mechanically. (i.e. the same as C -- even if you have no idea what/why it's doing at a more meaningful level, like what's a bessel function, there's some value just in knowing that a chunk of code idiomatically isn't depending on or doing too much crazy stuff outside the context.) Random short snippet:

    (defun lookup-reduction-label (obj)
      (let ((task
              (etypecase obj
                (primitive-node (operator-task obj))
                (list obj))))
        (gethash task *reduction-labels*)))
Yup, pretty normal looking to me. Might be better as a pair of defmethods. Easily understandable mechanically even if I don't yet know what a primitive-node is, or any of the application specific concepts really. I don't fault C if I don't know what a vma->vm_page_prot is, I just know it's a field on a vma struct that's getting dereferenced, and nothing else super crazy because -> can't be overloaded in C.

You'll find the occasional macro in such lisp projects, but it's unlikely to be an impressive display of macrology that requires sitting down to study it. Same thing with most popular libraries. Most Lisp code doesn't actually have much "spooky action at a distance stuff" an otherwise innocent looking line of C++ can be known for like implicit conversions, copies, confusing precedence, or overloaded operators. Even Lisp's take on exception handling (the condition system) isn't so spooky despite allowing more control flexibility, simply because it doesn't automatically unwind the stack and allows for restarts -- you can resume execution from where the condition was signaled. The worst you'll tend to see somewhat frequently are method calls that may have surprising :before/:after/:around dynamics. But now here's my Java apologist talking: Lisp can be thought of as more of a programming system than a language, and as such in practice the context of working with Lisp is with a Lisp-aware editor, so it's not exactly fair to compare it (or modern Java) with another language if your arena of choice is black and white paper print-outs on a desk rather than a typical working environment. This means, to address that spooky action possibility, you can resolve your curiosity at any time by calling compute-applicable-methods and compute-effective-method. In addition, you have things like intellisense on demand for symbol/function/macro documentation, ability to jump-to-source (even of the lisp implementation's functions), ability to cross-reference callers and callees of something, ability to macro-expand any macro call, ability to disassemble a function, ability to compile changes to functions, and so on and so on. This is also all pretty much built in to the language itself ("compile-file" is a function available at runtime) and just more conveniently exposed via Lisp-aware editors.

I don't do much C anymore. Three projects that came to mind: sqlite, the linux kernel, and atril (pdf viewer bundled with mate desktop environments, forked from the old gnome2 evince). Take random files from these projects. I don't think you can really claim that you can just take snippets more or less as-is and use them in some other project. Part of the problem again stems from C's lack of expressive power along with other weaknesses like an impoverished runtime -- which larger projects are going to feel the pain of even more acutely, and thus use different and incompatible methods of solving that. But the other part of the problem is just a nature of any project in any language: it's made up of its own data abstractions that are highly relevant to that project. Re-use at the snippet level is rare. You aren't going to gain anything from (dart at wall) sqlite's mutex.c file if you try to cherry pick snippets. Everything depends on its own struct (which is different for three platforms -- hey, that's useful to study and maybe copy, but there's enough specific sqlite stuff even in the short definitions that you can't literally just copy the code over), you can't even re-use anything that allocates because it calls their own sqlite malloc that does whatever differently from a system malloc... Picking a random linux kernel file, io_uring/cancel.c, what are you going to be able to extract from this? It's straightforward C code, yes, but it's intimately tied to the context. There are references to mutexes in it -- how much do you want to bet you can't just use sqlite's notion of a mutex in place, or vice versa? (Sqlite uses a pthread mutex; needless to say the struct layout is not compatible with the kernel's mutex.)

Atril came to mind because last year I hacked it so it would show me a time estimate of how long it'd take to reach the end of a book when I have it auto-scrolling. I dived in and noticed types like gboolean and gint. Oh great, custom typedefs over basic things, like so many other C projects, what do I have in store... at least they're sensible. https://github.com/GNOME/glib/blob/main/glib/gtypes.h#L56 But yeah, to make a long story short, it uses glib. glib is an amazing and kind of cursed library to bring a lot of higher level language features to C. Except (at least to me, encountering it with the goal of understanding code for the first time) without the normal tools of high level languages that aid in development and understanding. I pieced what I needed to piece together and made my hack work, but it involved quite a few printfs. (Incidentally, Common Lisp includes the standard function "trace", which you can call at any time, applied to a function, and from then on when that function is invoked its input args and output values will be printed. You can "untrace" it later. Editors have hot keys.)

Re: A Lisp adventure on the calm waters of the dead C (2021)

#26

The author of this blog is clearly eloquent and, as per their interspersed quotations of David Hume and others, it is refreshing to see someone so well-read in the software/tech blogosphere. I love Lisp. The last few paragraphs are a pretty good description. It's nice to have a very flexible set of tools, instead of being forced to conform to object-oriented design or whatever paradigm. IMO the only legitimate reason…

The author may be eloquent, but unfortunately calls imperative operators, like while, "functions".

Re: A Lisp adventure on the calm waters of the dead C (2021)

#27

The author of this blog is clearly eloquent and, as per their interspersed quotations of David Hume and others, it is refreshing to see someone so well-read in the software/tech blogosphere. I love Lisp. The last few paragraphs are a pretty good description. It's nice to have a very flexible set of tools, instead of being forced to conform to object-oriented design or whatever paradigm. IMO the only legitimate reason…

The author may be eloquent, but unfortunately calls imperative operators, like while, "functions".

Imperative code is a function from one program state to another.

Re: A Lisp adventure on the calm waters of the dead C (2021)

#28
Coincidentally R is one language in which `if` and `while` can be written as functions, because all function arguments are lazily evaluated, and one can get access to the underlying lambda for repeated re-evaluation. In fact, `if` and `while` are functions in R, and you can call them as such if you properly quote the keyword so that it's treated as an identifier. And then the familiar C-style syntactic forms are just syntactic sugar for function calls.

R takes it up a notch though by making all syntactic constructs boil down to a function call. Function definitions are themselves calls, for example, and so are assignments and even curly braces.

Re: A Lisp adventure on the calm waters of the dead C (2021)

#29

typedef int fn_t(int, int); int iff(bool cond, fn_t a, fn_t b) { if (cond) return(a()); else return(b()); } Now just write the implementation in terms of a() and b(). I don't get it. C doesn't have convenient syntax but this is compiled and not an evaluated language. This argument didn't make sense to me.

I have the same issue with this article. I think it actually complains about the lack of inline anonymous function declarations.

The fact that you can't create functions at runtime is only due to needing a compiler. If you link your program against a compiler you can absolutely turn strings into code at runtime and then pass it around to other functions.

Re: A Lisp adventure on the calm waters of the dead C (2021)

#30
post #21

I see mixed comments, so let me add some praise. I am one of countless, who match his intro-filter: repeatedly hearing 'enlightened' people lament that the vast masses don't "get" lisp and FP, and repeatedly attempting/failing to pick up the red string myself. background - I am a computer science major with 30+ years experience. I did do a mandatory class of 'implement your own lisp' many eons ago. It just never real…

Lisp is "functional" in a 1970s sense in that it has functions as first-class objects you can pass as parameters to other functions, but those functions are basically subroutines which can have side-effects and un-functional behavior. As you allude to, this is pretty much par for the course in procedural languages now, and OO As She Is Spoke is procedural with some extra stuff added. Even garbage collection is common…

To be fair, the fact that the IO Monad is in fact a monad is a sort of quality of life solution. Monads themselves don't have any side effect implications
Post reply on HN