Hacker Newsnew | past | comments | ask | show | jobs | submit | drmeister's commentslogin

Awe-inspiring work Shinmera and Charles.


Dang, substitute Lisp for Prolog and this describes me. Seriously though - Prolog is an awesome tool to have in your toolbox. I've implemented Prolog-like logic programming solutions in several places in my 40+ years of programming. Like rules for assigning molecular mechanics force field atom types.


> Like rules for assigning molecular mechanics force field atom types.

Can you describe a bit more how prolog helped you here? Thanks!


I accidentally a Common Lisp that interoperates with C++ (https://github.com/clasp-developers/clasp.git). We would also like to move beyond BDWGC and the Whiffle GC looks interesting. I will reach out to you, and maybe we can chat about it.


The big problem I have with locks is under high use, they slow parallel code down MORE than if you ran it serially. This is because of "false sharing" when one core grabs the lock, it clears the cache line of every other core that will grab the lock, and they have to fetch from the main memory to read the lock. Fetching from main memory is very slow. I use compare-and-swap in those cases, and almost every lock I've used I've converted to compare-and-swap. I'd like to explore "restartable sequences" for parallel data structures (https://tinyurl.com/vzh3523y) but AFAIK this is only available on linux and we develop on MacOS and Linux.


What you describe (multiple cores sharing the same lock) sounds like true sharing, not false sharing. False sharing means cache line contention is when cores are using separate structures (i.e. not actually shared) which happen to be on the same cache line.

Compare and swap still typically requires the cache line to bounce around between cores, so if that’s the primary cost of locks for you it seems like compare and swap doesn’t really fix the problem of frequent synchronization between cores.


Ah - thank you. "True sharing" makes way more sense to describe the situation. The general problem with locks I've seen again and again - and maybe CAS doesn't improve it, but I thought it did. It is challenging to profile this stuff carefully. I've been disappointed many times by my attempts at multithreaded programming when locks are involved. I've implemented a multithreaded Common Lisp programming environment (https://github.com/clasp-developers/clasp.git) that interoperates with C++. Two locks that cause me massive headaches are (1) in the unwinder when we do a lot of stack unwinding in multiple threads and (2) in the memory manager when we do a lot of memory allocation in multiple threads. Parallel code runs multiple times slower than serial code. In another situation where we do parallel non-linear optimization without allocating memory, we can get 20 CPU working in parallel with no problems.


If false sharing is truly the bottleneck in your code, can't you just make sure the lock reserves the entire cache line? Each core still needs to fetch from L3 cache to grab the lock itself, but that's roughly the same cost as a compare-and-swap.


The concurrent queue code in Java has/had an array the locks were placed in. Now the problem with putting locks in arrays instead of scattering them across the heap is that you get false sharing immediately. So the array is 8 or 16 times as big as it needs to be to get one lock per cache line.


The @Contended annotation will pad the memory layout of fields to fill a cache line and avoid false sharing.


Get `udb` - the reversible/time-traveling debugger from Undo. I don't own any stock - I love their product. You can run your code within it, hit an error, set a watch-point, reverse-continue to where the watch-point memory was changed and then check the stack. udb will turn brain-melting, blood-freezing memory bugs into trivial problems that you can solve in a few minutes.


A similar project is rr[0], which is freely available. Like you said, I find that reversible debuggers are a huge improvement over regular debuggers because of the ability to record an execution and then effectively bisect the trace for issues.

[0]: https://rr-project.org/


In our latest release of UDB we added the `last` command: https://docs.undo.io/TrackingValueChanges.html

Which effectively combines a `watch -l`, plus a reverse-continue but also monitors when the underlying memory was allocated / freed.

It's quite nice, basically "git blame" for your variables.


(author here) Yes I tried reversible debugging a couple years ago, but at the time I didn't have any bugs that needed it :) I managed to get by here without it, but it's a technique I'd like to be more familiar with

I'm on the Undo mailing list as well -- nice to see that it's effective!

(Also should say that Clasp sounds very cool -- I'm a fan of anything that enables interop and reuse, rather than rebuilding the same thing in different languages, which may or may not be as good)


Thanks! I'm very interested in a new garbage collector that works with C++. I have a long list of requirements we need that is currently satisfied by the Boehm garbage collector and were satisfied by the Memory Pool System before we moved away from it. I've been looking at the Memory Management Toolkit (MMTk). Where would you put Oil in that small club of memory managers?


I've been following along the gc of oil.

It succeeds because it only solves the GC problems that Oil has (and I mean this as a high complement).

Oil is single threaded, code runs in loops that are well understood, and the code is generated from a strongly typed subset of Python.

The GC then is run only between loop iterations, so there is no need for stack scanning. You never have to worry about a root being in a temporary (from the point of view of the C++ compiler) since there are just a few locals in the function running the loop, and any variables local to the loop are logically dead between iterations.

Since a goal of Oil is portability, not scanning registers and stacks is very important. Getting this right when the GC could be invoked at any allocation is potentially intractable with mypy semantics at least.


Ah - thank you. We are looking for a GC that supports (in no particular order): (1) conservative stack scanning, object pinning. (2) optionally conservative and precise on the heap. (3) compacting when precise. (3) weak pointers. (4) good multithreaded performance. (5) finalizers. (6) ability to create pools of different kinds of objects - especially cons cells (pairs).


Probably not coincidentally, that sounds like SBCL's cgc...


You can look at this one: https://github.com/pebal/sgcl


So I'd say Oil's collector is highly unusual and not applicable most problems! (I now think that "every GC is a snowflake" -- it's such a multi-dimensional design space)

It's unusual because it's a precise collector in C++, and what I slowly realized is that that problem is basically impossible for any non-trivial software, without changing the C++ language itself :)

It seems like that hasn't happened, despite efforts over decades. I added this link about C++ GC support to the appendix, which also explains our unique constraints.

Garbage collection in the next C++ standard (Boehm 2009)

https://dl.acm.org/doi/abs/10.1145/1542431.1542437

http://www.oilshell.org/blog/2023/01/garbage-collector.html#...

---

The reason that precise GC can work for Oil is because it's a shell that links with extremely little 3rd-party code, and has relatively low perf requirements. We depend on libc and GNU readline, just like bash. And those libraries are basically old-school C functions which are easy to wrap with a GC.

(Also as Aidenn mentioned, shells use process-based concurrency, which means we don't have threads. The fact that it's mostly generated C++ code is also important, as mentioned in the post)

---

The funny thing is that one reason I started this project is because I worked with "big data" frameworks on clusters, but I found that you can do a lot on a single machine. (in spirit similar to the recent "Twitter on one machine post" https://news.ycombinator.com/item?id=34291191 )

I would just use shell scripts to saturate ~64 cores / 128 G of RAM, rather than dealing with slow schedulers and distributed file systems.

But garbage collectors and memory management are a main reason you can't use all of a machine from one process. There's just so much room for contention. Also the hardware was trending toward NUMA at the time, and probably is even more now, so processes make even more sense.

All of that is to say that I'm a little scared of multi-threaded GC ... especially when linking in lots of third party libraries.

And AFAIK heaps with tens or hundreds of gigabytes are still in the "not practical" range ... or they would take a huge amount of engineering effort

---

But of course there are many domains where you don't have embarrassingly parallel problems, and writing tight single- or multi-threaded code is the best solution.

Some more color here: https://old.reddit.com/r/oilshell/comments/109t7os/pictures_...

I wonder if Clasp has any support for multi-process programming? Beyond Unix pipes, you could also use shared memory and maybe some semaphores to synchronize access, and avoid copying. I think of that as sort of "inverting" the problem. Certain kinds of data like pointer-rich data is probably annoying to deal with in shared memory, but there are lots of representations for data and I imagine Lisps could take advantage of some of them, e.g. https://github.com/oilshell/oil/wiki/Compact-AST-Representat...


Thank you. We do precise GC in C++. I wrote a C++ static analyzer in Lisp that uses the Clang front end and analyzes all of our C++ code and generates maps of GC-managed pointers in all classes. We precisely update pointers in thousands of classes that way. We also use it to save the system's state to a file or relinked executable so we can start up quickly later. Startup times using that are under 2 seconds on a reasonable CPU.


Ah OK very interesting ... So the tracing is precise, but what about rooting? From your other comment it sounded like that it can be imprecise. But maybe it's for dynamic linking where you don't have source access?

In any case, I would imagine embedding a C++ compiler at runtime does open up a lot more options!


gdb has had reversible debugging since release 7, in 2009. What does udb offer that it lacks?


(I work for Undo)

> gdb has had reversible debugging since release 7, in 2009. What does udb offer that it lacks?

GDB's built-in reversible debugging is cool (and it's helped raise awareness) but it doesn't scale well. We build on the same command set and serial protocol that GDB defined - UDB is GDB but with additional Python code hooking it up to our separate record/replay engine.

For UDB, I'd say we offer: 1. Performance & efficiency (orders of magnitude faster at runtime and lower in memory requirements). 2. Recordings can be saved to portable files (share with colleagues, receive from customers, etc). 3. Library API so applications can self-record with control of when to capture and save. 4. Wider support of modern software (proactively tracking modern CPU features, shared memory and device maps, etc). 5. Correctness (in the past we've found the reverse operations in GDB don't have as strong semantics as we'd hoped, though I'd also be happy to be wrong here)

FWIW, rr (https://rr-project.org/) also offers many similar benefits over GDB's built-in system (though not the library API in point 3) but with differences in what CPUs / systems are supported, ability to attach at runtime, etc.

If you're looking for an open source solution, I'd choose rr over GDB's built-in approach.


I'm not familiar with udb, only with rr. Compared to rr, gdb's recording for reversible debugging is tremendously slower. However rr has strict requirements for the CPU (didn't work on AMD, the last time I looked) and requires some permissive perf_event_paranoid setting to run.

In my experience rr's recording is around x2 slower for single threaded programs than running the program on its own.

I think featurewise they are in parity.

I heard that udb is like rr, but better on some fronts (except price and software freedom).


gdb's built-in recording slows down the debuggee by about 1000x, rr more like 2x. That makes a huge difference in practice.

rr's recording works across system calls, thread context switches, etc, but gdb's doesn't.

rr's recording creates a persistent recording on disk that you can even move around between machines. This permits workflows that gdb's doesn't.

(Disclaimer: I work on rr)


The GDB JIT interface implementation is seriously flawed. I love GDB - but this causes me a LOT of grief when debugging clasp (Common Lisp implemented using llvm as the backend https://github.com/clasp-developers/clasp.git).

Every time a JITted object file is added using the API, the entire symbol table is sorted. If you have 10,000+ JITted object files as we do - it takes hours to days to weeks to register them all.

We use the Undo time traveling debugger that builds on top of GDB. It's awesome but we are crippled because of the JIT API implementation.

I'd love to see this get fixed - if anyone knows who to talk with about it - drop me a line.


Have you peeked into it? What's the implementation like in there? Anything you could replace with an incremental merge instead of a full resort each time?


Thanks for that patch - I'll watch this with great interest.

Is there a problem if dynamic libraries invoke dlopen/dlclose they also need to call the sync function - correct?

I'm asking because we've developed a Common Lisp implementation that interoperates with C++ and it uses exception handling to unwind the stack (https://github.com/clasp-developers/clasp.git). We hit this global lock in unwinding problem a lot - it causes us a lot of grief.


This should be the top response. With a time traveling debugger this would take 15 minutes to solve (minus the time to make the crash happen). On linux I use the Undo debugger - I don't own stock - I just love the product.


I like the term "Eternal Language" - programming languages where if you write code now - you will be able to compile and use that code ten years from now (a software lifecycle eternity). Common Lisp, C, C++, Fortran, (edit: Java) are close to eternal languages. After losing a huge amount of Python 2 code (I wasn't going to rewrite it) I implemented Clasp, a Common Lisp implementation that interoperates with C++ (https://github.com/clasp-developers/clasp.git) so that I can write code that will hopefully live longer.

I am one of the few software developers with code that I wrote 27 years ago that is still in active use by thousands of computational chemistry researchers (Leap, a frontend for the computational chemistry molecular dynamics package AMBER, implemented in C).


In the realm of spoken languages, they say for a language to become immortal, it has to die. Latin is the classic example of this. Languages that continue to evolve will continue to "break" so to speak.

In this vein, Standard ML (SML) is truly immortal, because the standard is set and it is "finished".

Just a fun thought!



Until "modern classics" education became a thing, Latin was arguably a still living language, if in limited use (mainly among the clergy, certain professions and educated circles, and effectively official language of certain countries).

Then ~17th century modern classics turned latin education into navel gazing on the topic of bunch of roman republic/empire era works, and disregarded actually using it.


If I remember correctly there were still some scientific works being published in Latin in the 1900s


I really enjoy modern translations like "night-club" -> "taberna nocturna".


Awesome also that this is perfect spanish.


"modern" language teaching killed Latin

before that they were teaching it without any stress on grammar, mostly memorizing phrases and short sentences and learning how to use it


Common Lisp's extreme extensibility thanks to macros means new features can be added without breaking the standard.


Firstly, programming languages are always extended when a program is written, because a program consists of new definitions. This is true of SML. Any SML program consists of the language, plus that program's definition of symbols, which effectively create a local SML dialect consisting of standard SML plus the program's vocabulary.

Secondly natural language evolution isn't controlled by people who know what the are doing, or care about language immortality. They sometimes break things just for the heck of it.

Random examples:

- the "great vowel shift" in the development of English: a linguistic buggery that caused what sounds like "a" in just about any language that uses the roman alphabet, to be written using "u".

- random reassignments of meaning. For instance, the concept called "sensitivity" today was connected to the word "sensibility" just around two hundred years ago. That's pretty gratuitous; I could easily live with a branch of English which had kept it the way it was.


I could happily live with an English that didn’t accept literally to mean “not literally”.

Informal: used for emphasis or to express strong feeling while not being literally true. "I was literally blown away by the response I got."


But that English may have never existed! "Literally" has a long history of being used to mean "a kind of figuratively that is almost perceived like it's really happening" or something like that; perhaps as long as the word itself.

What I could live without is the dialect of English in which "literally" is used to explain away some figure of speech as not being a figure of speech, whereby, oops, there is actually no figure of speech to explain away.

Like "I literally just moved to this town yesterday, so I don't know my way around yet".

What is that for? Nobody suspects that you figuratively moved into this town yesterday; if you moved here just yesterday, then "I moved to this town just yesterday" is perfectly adequate.

(Now being born yesterday is a genuine figure of speech; we could make a meme in which a speech balloon next to a newborn infant's mouth makes a joke about literally having been born yesterday: that would be a valid use of literally.)

Since being blown away is a genuine figure of speech, the expression of "literally blown away" which refers to a situation which is still only figurative is a well-entrenched use of the word: hundreds of years old, I think. It says that the figure of speech is so fitting that if you imagine me being literally blown away, that is actually fairly accurate, so much so that when the inevitable film adaptation is made of my life story, special effects will necessarily hav to be used to portray it that way.


It’s just become a word of exclamation, hyperbolic at that, no? If someone literally moved to a town yesterday, they probably mean neither literally nor figuratively, but rather, just highlighting their short tenure at the new place.

I’m a bit of a language pedant, but find it difficult to argue with the society assigning new meanings to words as the language evolves. Modern English is a tragically malformed and bastardised version of Saxon mixed with French, after all.


You'd have to go quite a ways back for that one.

Charles Dickens, "Nicholas Nickleby" -- "[h]is looks were haggard, and his limbs and body literally worn to the bone"


We can look at it like this: kidding and hyperbole aren't bad grammar. Limbs could literally be worn to the bone; that they are actually not isn't a matter of grammar.

The character is fictitious in the first place, so we could argue that "his looks were haggard" is misusing the word were, since were may only refer to a person that existed. :)


Damn, great vowel shift sucks.

Learning English in school was really weird.

Finnish as a language is almost entirely pronouncially composable. If you know how every single letter is pronounced, you can also pronounce any word.

In English there are all these silent letters, different pronounciations depending on the position of the letter etc. And the vowel shift: "a" written as "u", "ai" written as "i", "i" written as "e" etc.


>Finnish as a language is almost entirely pronouncially composable.

So is Sanskrit. It is supposed be one of the most logically-designed languages.

>If you know how every single letter is pronounced, you can also pronounce any word.

Again, similar in Sanskrit. Taking it further, it is perfectly acceptable to make up words of your own by combining two or more words.


I think I've recently read that the amount of Akkadian/Sumerian material is vastly more, than anything the Latin period has produced. Yet almost nobody is able to interpret it, in comparison to Latin.


There's actually been a decent amount of work on SuccessorML to add a few things to the language.


I hope that Rust joins the pantheon of eternal languages. I feel like it has a lot of the advantages of a language like C, but is just more modern and well thought through.

Also, you did not mention Java. I have very old Java code that still works fine.


Java is probably the biggest "eternal" language we have these days. Probably tied with C and C++.

There's <<so much>> Java code churned out that it's unbelievable. Business systems have a ton more code than infrastructure systems.


> Java is probably the biggest "eternal" language we have these days

Is it? I haven't followed it in years but i remember reading about people sticking with Java 8 because later versions broke backwards compatibility in some cases.


Its a bit complicated, technically java language never broke backward compatibility. you can run existing java programs fine in higher java jvm's by adding extra command line parameters to include all existing libraries in default module. But it becomes complicated by the use of intermediate tools which are not updated to handle this.


Yep, modules were a big break, however all the relevant libraries that actually matter on the Java ecosystem are available on latest versions.

Basically it is the same kind of effect like school/university teachers whose idea of C++ is C with extras, and if they teach something more close to C++98 proper even in 2021, one should consider themselves lucky.


> [java] Probably tied with C and C++.

i think java wins over c/c++, because there's very little low level, machine specific code you can do in java, where as you surely can do that in c/c++.


True, and instead of rewriting the whole stuff, if some C or C++ is actually required there is always JNI, JNA, or the upcoming Panama.


I would check on cobol first.


There's a ton more Java out there. Java is much newer but it's from a time when we're talking about tens of millions of developers worldwide, instead of tens of thousands. A lot more people have been churning out Java since 1996 than Cobol since 1955 or so.


Do you have a feeling for how many Java LOC are out there?


I've worked for a 10 person company where 5 were working on a Java backend, the app was about 10 years old. 250k lines.

I used to work for a mediocre multinational with about 50 people working on a big Java front end to their many servers. Just the frontend was about 3 million lines.

You've never heard of either of them, the multinational had a market cap of about half a billion.

I've worked for a bunch of bigger companies but I don't have numbers, I just know they had bigger systems.

I wouldn't be shocked if there are tens of billions to hundreds of billions of lines of Java in production right now. I imagine that something like half a billion are added each year.

There are so many big Java middleware companies nobody has heard of. You wouldn't even know they use Java if you wouldn't look at the job listings.


If you multiply the 5000 LoC each developer at your 10-person company wrote yearly by the tens of millions of Java developers in your previous comment, you get much more than half an (American) billion


That's going to be quite a loaded metric considering how verbose Java is.


Java is at most longer than “short program” by a small constant factor, and that is mostly around project initialization.

Also, I have never really understood it — c++ is not the shortest thing either with duplicating headers/implementations, go’s error handling deserves a whole discussion in terms of verbosity yet these languages are not considered repetitive.


Well as a former cobol programmer, I would argue that it is no less verbose than pretty much any language.


C# is also probably in a similar league, for the same reasons.


> I hope that Rust joins the pantheon of eternal languages.

I'm torn about this. On one hand, Rust is so much better than C it is ridiculous and I really hope it does become a language with decade-long longevity.

On the other hand, Rust is the first new "systems programming" language in forever and is finally prying the door open to something other than C/C++. I'm really hoping that Rust opening the door and paving the way means that now we can get something better.

What worries me about Rust is the impedance mismatch down at the very primitive hardware level--bytes and registers. The embedded guys are doing an amazing job papering over it, but the abstractions leak through quite a lot and you have to twist things around to satisfy them.

The problem is: that's a lot of fiddly code with all kinds of corner cases. So, you either have a lot of work or you throw up your hands and invoke C (like Zig does).


I'd give it another 2-5 years before the syntax settles. Some serious improvements have recently been pushed out and I wouldn't be surprised if further improvements are on the horizon.


editions still allow backwards compatibility though, it's not like the syntax changes will break it


Rare example of broken backward compatibility: `cargo install gltf-viewer` doesn't work today because of an error in gltf library. The error is fixed, but gltf-viewer is not updated to use the fix.

   Compiling gltf v0.11.3
  error[E0597]: `buf` does not live long enough
   --> /home/vlisivka/.cargo/registry/src/github.com-1ecc6299db9ec823/gltf-0.11.3/src/binary.rs:225:35
      |
  119 | impl<'a> Glb<'a> {
      |      -- lifetime `'a` defined here
  ...
  225 |                     Self::from_v2(&buf)
      |                     --------------^^^^-
      |                     |             |
      |                     |             borrowed value does not live long enough
      |                     argument requires that `buf` is borrowed for `'a`
  ...
  233 |             }
      |             - `buf` dropped here while still borrowed


I'd like it to be so, but i dont have faith for it. rust doesnt have a standard like lisp/c/c++ and it stands to those languages for their eternality, and at least with lisp and C their relative simplicity makes all the difference for something like that. they feel like, despite how opposite they are, discoveries rather than inventions when compared to something so complex like rust and c++


I don’t think Rust will (at least not in current form). It’s breaking too much ground in the (real world) PL-design space. We just don’t know what really work yet!


Eh; sum types, everything-is-an-expression, and composition over inheritance get you very very far toward optimal language design. Sure there are some potential economic improvements that could made to Rust, but the foundation is so strong that I don’t see anything that makes those same design decisions supplanting Rust. More likely that a new Rust edition would incorporate those kinds of changes.


Java is younger than Python.


But Java 1.0 code is still (mostly) compile-able on recent Java versions, not so much with Python where I'd worry about going back 5 years.


Many Python developers and users were burned when the Python project decided to set fire to billions of lines of code.

I have zero trust in Python as far as code longevity is concerned.


More like trillions of lines of code, from probably millions of developers, dating back to the 90's.

"The total code size of Zope 2 and its dependencies has decreased by over 200,000 lines of code as a result." - from the 2013 Zope documentation... how many lines of code was it before then?

Python really burned its bridges. It's shown that it's a toy language now, and demonstrably unfit for any real production-quality projects.


> how many lines of code was [Zope] before then?

Soooo many. Zope was a very formidable code base to delve into. I was trying to learn it because my company was using Plone, and Zope quickly surfaced through the abstractions.

Zope's codebase might have been more accessible if type annotations were a thing back then. Their implementation of "interfaces" for Python were very interesting back in the Python 2.3 days.

I disagree that Python is a "toy" language now. It started out as one, and has been stumbling awkwardly away from that ever since the mid 2000s - virtualenvs, pip, pyenv, pipenv/poetry, type annotations, mypy etc.

In my entirely unscientific opinion, I think it was Django that started this journey, then of course scikit, numpy leading into pandas etc.


Both NumPy and Django were created in 2005, but Numeric, the ancestor of NumPy, is nearly as old as Python itself, and predates SciPy.


Bullshit. 2 => 3 transition was mostly mechanical and programmers who are unable to do it should get better.

I haven't seen py2 code in several years. Granted, I would look away in disgust if I did.


Right right right. I'll let my CTO know.


I wrote code that worked with both interpreters simultaneously for many years. I don't know why you'd worry so much.


> programming languages where if you write code now - you will be able to compile and use that code ten years from now (a software lifecycle eternity)

Fun fact: code written in JavaScript in 1995 will work in 2021, after 26 years.

If you are talking about "good practices" - few weeks ago I worked with C code from 2001 and it was just awful. Yes, it compiles - but it wouldn't pass any modern code review.


> Fun fact: code written in JavaScript in 1995 will work in 2021, after 26 years.

Sure, as long as you run it in Netscape Navigator 2.0.

In 2021 you're lucky if a JavaScript web app that worked fine on Tuesday still works on Wednesday. And shocked if it works in any browser other than Chrome.


It's plainly untrue. I worked on ECMAScript proposals and TC39 takes backwards compatibility really seriously.

Browsers too take backwards compatibility very seriously and features stop working almost only if these are serious security holes.


You're right about backward compatibility - and yet so many things break anyway. And many web apps of the past only worked on Internet Explorer (much in the way that modern web apps only work on Chrome.)

However, the thing that broke Tuesday's app on Wednesday was probably a library or framework change.


That's the DOM manipulation and browser behaviors, not Javascript itself.


If you divorce JavaScript from the Dom you have removed it's entire reason for existence. So the dom is effectively part of the language. Just as the standard library is effectively part of C or Nim, etc. Without them they are considerably less useful.


You remove the reason -for its creation-. Not its reason for existence; Node is a thing, and doesn't touch the DOM. So, no, it's not effectively part of the language.


Ah, but code written in Common Lisp in 2021 will usually work in 1995, after -26 years.

:)


Aside from CL implementation bugs. The implementations had lots of them back then, even the commercial ones. Nowadays, they are much better. Decades of effort to make an implementation bug free and shiny will have its effect.


That really is amazing. Is it true, even "usually", for most types of code? Both forwards and backwards compatibility.. it's like the holy grail.


For most types of code, other than things like code which has dependencies on implementation internals (like extensions in SBCL for instance), or uses FFI to access platform libraries that didn't exist in 1995; that sort of thing.

ANSI CL was last ratified in 1994; there has been no standard language change, and the community language improvements are just macro libraries. You should be able to take your alexandria utilities library, or optima pattern matching or whatever back to 1995.


There are a few community improvements that are language extensions. The standard had no notion of multithreading, MOP was not in the standard, and packages really needed some way to avoid collisions of short package names (which lead to the de facto standardized package local nicknames.) Pathnames may also have seen some de facto standardization; they were poorly specified in the standard.


Maybe the C code was just badly written


Yep.

Use some of the -W* flags (-Wall -Wpedantic -Wconversion, ...) and specify the standard -std=c90.

Avoid undefined behaviors:

- https://en.cppreference.com/w/c/language/behavior

- https://wiki.sei.cmu.edu/confluence/display/c

(Also use cppcheck, valgrind, gdb, astyle, make, ...)

Done.

Fun fact: JS and C are both standardized by ISO.


And always use "-fwrapv -fno-strict-aliasing -fno-delete-null-pointer-checks". Those are source of the hardest to reason about UB and the small performance benefit is not worth the risk. I would love to always be certain I haven't accidentally hit UB, but that's equal to the halting problem, so for peace of mind, I just ask the compiler for a slightly safer variant of the language.

P.S. and old code definitely needs them, as at the time compilers didn't optimize so aggressively and lots of code does weird stuff with memory, shifts, etc.


Have a look at Apache 1 (C) or KDE 1 (C++); it'd be interesting to see what you think of those codebases, and they can certainly predate 2001.


Maybe it's next to impossible to write C code that isn't badly written


Or maybe we have too many developers that treat language of their choice as a religion.


>To encourage people to pay more attention to the official language rules, to detect legal but suspicious constructions, and to help find interface mismatches undetectable with simple mechanisms for separate compilation, Steve Johnson adapted his pcc compiler to produce lint [Johnson 79b], which scanned a set of files and remarked on dubious constructions.

-- https://www.bell-labs.com/usr/dmr/www/chist.html

Unfortunately since 1979, majority of C devs think they know better.


C code is of course close to the hardware, which as always, has benefits and drawbacks.


I was pretty sure that there were some subtle changes in ES5 and ES6 that were technically not backwards compatible (done because very little code depended on those behaviors)


Out of curiosity: would the 1995 JS code pass any modern code review?


I’d imagine it would get pinged for using var instead of let or const. And depending on who’s doing the review they might not like ‘new’.


>"I’d imagine it would get pinged for using var instead of let or const."

This single thing in isolation should never cast code as good or bad.


JS from 2003 passed code review for me a few months ago... so, yes... maybe.


Probably depends what it's doing. If it's interacting with HTML and manipulating the DOM then probably not


How do modern frameworks display content? Must be interacting with HTML and manipulating the DOM in some way.


It's not whether they do, it's how they do.

For instance, in 2003 there was no such thing as a mobile phone; passing code review today is probably going to entail either being responsive, or directing to a mobile version when accessed from a mobile device.



Okay, I will clarify my hyperbole in the face of the pedantic - mobile phones did not provide ubiquitous, standardized approaches to browsing the web, such that UX guidelines for the web did not include them; media queries and such did not exist except as drafts, etc etc.


I think that to earn the title "Eternal Language" that should have to work in both directions.

If I am an expert in language X and take a solar powered laptop and go spend 10 years living as a hermit in some isolated place while working on some big X program, with no communication with the outside world other than idle non-technical chat with the people of the village I go to monthly to buy supplies, if X is an Eternal Language then

1. My code that I wrote as a hermit should build and run on the outside world's systems,

2. I should still be an expert in the language X, only needing to learn library changes, fashion changes (such as coding style changes), and tooling changes to be ready to take a job as an X programmer at the same level as I had before I became a hermit.

Alternatively, suppose I don't know X but wish to learn it. To earn the title "Eternal Language" I should be able to go into my library and read the "Learning X" book I bought 10 years ago but never got around to reading and that should mostly be equivalent to buying and reading a recently published book on X.


Go is another that strives for backwards compatibility: https://golang.org/doc/go1compat

After seeing the Perl5/6 schism, I was hopeful that the Python devs wouldn't make the same disastrous mistake, but.. the same happened with us with approximately 100k lines of Python 2 code (which we are now in the process of porting to Go and Rust), precisely for this reason.

It's exceedingly difficult to form trust in language developers who are willing to break working, production code in use all over the world for decades over a new unicode type and a few wishlist library items.


While Go strives for backwards compatibility, it's a bit too young for now at 12 years old, and already had a few changes around packaging and stuff like that. If we get to 20 years without Go 2 that will be a solid signal on the longevity of Go.


One of the newer (newer than C, C++, Common Lisp, ...) languages I feel comfortable about regarding this is Go. Definitely feels like I can write a simple Go program to generate a static website for example and it'll probably still work in that same state for a while.


It’s hard to say with any 1.x versions what’ll happen come 2.x


True, but... most languages wouldn't be on version 1.x after the amount of time (and progress) that Go has. Go has a track record by now, of being very careful about breaking things, and providing tools to help fix them when they do.

Sure, Go could introduce breaking changes in 2.x. Will they? Track record says "no".


As far as I can tell, the only reason for a Go 2 (as opposed to a Go 1.nnnn) is "we introduced a breaking change". Things that were considered "Go 2" are being worked in, as the language evolves, without the need to break previously written code.


Maybe Lua? Started in 1993 and LuaJIT is effectively frozen. I could see this one hanging in for a long time as the goto embedded language.


Interestingly, Perl doesn't get a lot of love on HackerNews but I would classify it in the family of "eternal languages."


Even after the Perl 5 / Perl 6 / Raku debacle? Many developers left after that (Python pulled off the same trick a decade later.. apparently some language teams have very short memories.)


Said debacle arguably reinforced Perl 5's eternity, if anything.


Especially after de Perl5/6 debacle. The two separate languages solution let them have fun on Raku, all the while ensuring that Perl 5/7/... will preserve retrocompatibility.


Some of that code has been running for a long time. A lot of the Perl toolchain still works on 5.8.1 (released Sept 2003).

https://metacpan.org/release/JHI/perl-5.8.1

Test::Simple still targets 5.6.2, which looks to have been released around the same time (November 2003).

https://metacpan.org/release/RGARCIA/perl-5.6.2


I have quite a bit of personal scripting in perl that I wrote in the early 90s, still in use every week. Haven't changed any of the code in decades.


C/C++ languages are stable, but building actual software with them is extremely cumbersome on future systems. It is unlikely to "just work" because of how we have inverted the dependency structure of the vast majority of C/C++ programs.


Ehhh. Integrating an old C++ library into a new project may be difficult due to build systems or not useful due to dependencies.

However if you want to take an old C++ game written in 2004 and ship it on modern platforms and consoles you only need to update a very small amount of platform specific code.

Updating a 20 year old C++ project is probably easier than updating a 3 year old web app.


Tbf if you’re just looking to run an app on newer platforms you’d likely not need to do anything with the web app. They have a lot of issues but once stuff works it’s usually a very long time before it doesn’t.

It would probably still be harder to add a new dependency to a 3 year old web app than it would be to integrate a 20yo C++ project though, I agree with you there.


imo that would be a "forever program" not really a "forever language." The lack of standard packaging and terrible paradigm of shared dependencies has made C/C++ incredibly fragile and non-portable when you need something from years ago to compile today.


JavaScript probably qualifies, too. It's not going anywhere for a very long time, if ever. Same with COBOL. COBOL will have to be killed with fire.

I'm assuming here that "eternal language" doesn't necessarily mean "good language". :-)


Unfortunately JavaScript code is often built on DOM and NPM quicksand.


How do you lose Python 2 code? The old interpreter still works.


But is no longer viable option for new work, and in this case a lot of the code IIRC was libraries for writing programs.


> "Eternal Language"

Even more than being able to compile and run it in a few decades (which is awesome) there is also the part that such eternal language ecosystems grow excellent tooling (debuggers, tracing, performance analysis, source tooling, build tooling, library maturity, etc) thanks to the stable platform and all the years dedicated to making it all ever better.

I greatly dislike the language of the year hype not so much because the language change itself, but because all the wonderful mature tooling doesn't exist in the new shiny thing so we're back to debugging via print and performance analysis via guesswork.


The only place LISP is eternal is on HN. A super-microscopic minority of engineers use it for real-world software development. The standard hasn't been updated for 2 decades. There is only one real compiler implementation. No WASM. No proper mobile support. Library support for real world tasks is abysmal.

Common LISP's tomb is indeed shiny and eternal. Developers at HN will mourn at its graveyard for eternity.


> There is only one real compiler implementation

what kind of meaning of 'real' reduces the several compiler implementations to just one?

Example: I use two native code compilers (SBCL and LispWorks) on my Mac. Both are available on other platforms, too. Is one of those not 'real'?

What about, say, ECL or Allegro CL? Are they unreal?


I honestly think most languages belong to this category. I can't really think of a language that changed so much that old code no longer works (other than python 2 -> python 3).


was it you that said that the difference between writing templates in c++ and writing macros in common lisp is like the difference between filling out tax forms and writing poetry :)


I had a very different experience. I implemented Common Lisp using LLVM-IR as the backend (https://github.com/clasp-developers/clasp.git).

1. I started with a primitive lisp interpreter written in C++ and worked hard on exposing C++ functions/classes to my lisp using C++ template programming. LLVM is a C++ library, the C bindings are always behind the C++ API. So exposing the C++ API directly gave me access to the latest, and greatest API. That means you need to keep up with LLVM - but clang helps a lot because API changes appear as clang C++ compile time errors. I've been "chasing the LLVM dragon" (cough - keeping up with the LLVM API) from version 3.something to the upcoming 13.

2. I wrote a Common Lisp compiler in my primitive lisp that converted Common Lisp straight into LLVM-IR. I didn't want to develop my own language - who's got time for that? So I just picked a powerful one (Common Lisp) with macros, classes, generic functions, existing libraries, a community etc.

3. I used alloca/stack allocated variables everywhere and let mem2reg optimize what it could to registers. I exposed and used the llvm::IRBuilder class that makes generating IR a lot easier.

4. Then I picked an experimental, developing compiler "Cleavir" written by Robert Strandh and bootstrap that with my Common Lisp compiler. It's like that movie "Inception" - but it makes sense :-).

Now we have a Common Lisp programming environment that interoperates with C++ at a very deep level. Common Lisp stack frames intermingle perfectly with C++ stack frames and we can use all the C/C development, debugging and profiling tools.

This Common Lisp programming environment supports "Cando" a computational chemistry programming environment for developing advanced therapeutics and diagnostic molecules.

We are looking for people who want to work with us - if interested and you have a somewhat suitable background - drop me a message at info@thirdlaw.tech


> 4. Then I picked an experimental, developing compiler "Cleavir" written by Robert Strandh and bootstrap that with my Common Lisp compiler.

I was wondering if this was some new twist on clasp that I was unaware of - but then discovered that I know that project as SICL (not cleavir).

Since you had a primitive cl compiler (from 2) - 4 added a runtime/advanced cl compiler?

https://github.com/robert-strandh/SICL


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: