A point the authors didn't make is that Common Lisp compilers are quite fast compared to e.g. C++ compilers. So even in rare cases where you do need to recompile everything, the cycle time is short.

CCL's compiler is lightning-fast. I can recompile an entire system in CCL almost as fast as I can load the compiled object code. SBCL's compiler is slower (while often generating faster code because it does more work at compile time), but it's still much faster than a typical C++ compiler.

Comparing it to C++ compilers... does Lisp have zero-cost abstractions?

It does, in form of a sane macro system with the full language available for use at compilation time - something that C++ never had and still doesn't have. No, consteval and constexpr aren't equivalent. No, CPP is not a sane macro system. No, don't even get me started about templates.

Portable CL is missing some key features for making zero cost abstractions such as unboxed arrays of structs.

Yes. That's a consequence of language design - unboxed arrays of structs mean no object identity, which means that operators like SETF and EQ no longer have their invariants satisfied when performing assignment to such arrays.

Sure, there may be reasons for it, but it means you can’t really build zero cost abstractions. For example, you can’t make a simple 2D vector object with the standard operations defined over it and then store those vectors in flat arrays. This is something that can trivially be done in C++, Rust, etc.

Yeah. Common Lisp has quirks and you need to adapt your abstractions to those. Sometimes it's easy, sometimes it's rewarding, sometimes it's just annoying.

This week I'm scaling back some abstractions, writing more Fortran-like code on specialized arrays than individual objects, for the sake of zero cost.

I appreciate a little bit of a headwind against inventing new abstractions too casually. But it does remind me of programming in C or Forth. That's not everyone's cup of tea.

This one isn’t a quirk, it’s a fundamental constraint on the kind of code that you can write in CL. C++, Rust, Go (and even Java to some extent, with all the wizardry in Hotspot) all allow you to build a 2D vector abstraction without requiring you to box your vectors. In CL you simply can’t do this. Just look at the kind of bonkers workarounds my comment gave rise to: https://news.ycombinator.com/item?id=35855576

I don't really see the problem. If you want to define the bit layout of your objects then you define them using FFI. Support for FFI objects is comprehensive. It is exactly the tool for the job in Common Lisp.

Same for LuaJIT. I've spent years happily writing high-level Lua code that's actually operating on objects whose bit layout is explicitly defined using FFI at the C level of abstraction. It doesn't feel much different to objects or dictionaries to me.

Sure, it is great that those other languages have native support for inlining storage of structs into various containers, but the lack of such in Common Lisp only makes me write quirky code and doesn't really hold me back.

It’s not a problem if you don’t need zero cost abstractions (which indeed you may not, depending on your domain). But if you do, then using the FFI to define unboxed arrays of a simple 2D point class is considerably less attractive than writing

    struct Point { float x, y };
    Point points[10];
If you really can’t see this then I think we’re at an impasse.

I can meet you in the middle on "considerably less attractive" :-)

For what it's worth, if I wanted zero-cost then the way I'd probably write that in Common Lisp would be with a more spartan abstraction like:

  (deftype f () 'double-float)
  (deftype points () '(simple-array (2 *) f))

  (-> euclidian-distance (f f f f) f)
  (defun euclidian-distance (x1 y1 x2 y2)
    (sqrt (+ (expt (- x2 x1) 2) (expt (- y2 y1) 2))))

  (-> point@ (points integer) (values f f))
  (defun point@ (points i)
    (values (aref points 0 i)
            (aref points 1 i)))
I'll grant you that's a kludge compared with your example. It wouldn't hold me back though. And I wouldn't consider trading in my lovely late-bound programming environment for an issue of this magnitude.

Try extending this approach to, say, a Polygon datatype and see how far you get :)

Hard to please some people :)

A design decision which needs to be made is at what level of abstraction should the data be "fixed", i.e. not to be manipulated with the full power of CL. This is often a flexibility vs. efficiency tradeoff.

In my 3D CL system [0], I have so far kept all geometric data as naive CLOS classes as the intent of the system is to provide a sandbox for experimentation. I have thought of, perhaps one day, representing the geometry as a foreign library for efficient passing to the GPU.

[0] https://github.com/kaveh808/kons-9