top | item 29855044

(no title)

dandotway | 4 years ago

Having learned a number of Lisp systems in the past, I wouldn't necessarily recommend ANSI Common Lisp as a first choice for a Lisp unless your needs are very particular, because it is an enormous design-by-committee language having a draft standard of about 1360 pages:

  https://lisp.com.br/archive/ansi_cl_standard_draft_nice.pdf
This means that in addition to the time spent doing the programming that solves your technical problem, you also have to devote considerable time to language lawyering, investigating if the interpretation of a Lisp expression your Common Lisp produces is or is not standard conforming, using only the frequently ambiguous English of that enormous standard as your guide.

The Java JVM was carefully designed to give identical results on all hosts for the Java platform unless you go out of your way to get nondeterminism or platform specific behavior (make the value of the number N depend on thread scheduling, or use JNI that assumes a platform byte order, etc.). C/C++/Rust allow undefined behavior if you shift a 64-bit int more than 64 bits, but Java on the JVM for example masks the lower 6 bits of a 64-bit shift ('& 0x3f') so that you get the same result on all CPUs rather than a CPU-dependent result:

  https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.19
The nice thing about this is that the JVM makes a "try it and see" approach more viable: if your program has a bug at least it has the same bug everywhere. You won't suddenly get a crash 10 years in the future when your customers upgrade to a new CPU, because your Java bytecodes on their new CPU will faithfully maintain bug-for-bug compatibility.

Clojure is a Lisp that runs on the JVM. I haven't personally used Clojure, but having done quite a bit of Common Lisp, Emacs Lisp, Scheme, etc., it looks to be very well designed and very well loved by its users, and using it could spare you from having to language lawyer the ANSI Common Lisp standard, as it seems to be more "try it and see" friendly.

I've been learning a lot about formal verification for C programs, how to truly make code that is bug free, and to do this you need to first make certain decisions about how you are going to formalize the language standard. E.g. will you assume that CHAR_BIT==8 or will you allow CHAR_BIT>=8 because the official ANSI/ISO C standard allows this even though all modern computers have CHAR_BIT==8? Then for any program you input to your verifier you must judge whether all its behavior is well-defined or if there is undefined behavior (arithmetic overflow, etc.).

There are quite good formal verification tools for Java, also, like JBMC and Krakatoa, and smaller Lisp languages are traditionally among the least tedious to formally verify (very simple semantics, unlike Common Lisp), but the time investment to learn these tools is enormous.

discuss

order

hajile|4 years ago

The Java VM Spec is almost 650 pages.

The core Java spec is another 850 pages. The core Java libraries add countless more pages on top.

The "CL is big" myth is a strange one. Big was in context of a 80286 in 1982. It already wasn't big by the 90s and a full-blown CL implementation is absolutely TINY compared to pretty much any other managed language you can think of.

I can't speak to the VM spec, but the 90s Java spec and the Common Lisp spec were both largely written by Guy Steel (who said that Java was meant as a stepping stone to shift C programmers a little closer to Common Lisp).

ISlisp and EUlisp both attempt to "correct" the Common Lisp language. Neither has been successful and the big reasons are that CL's core features aren't actually broken and the biggest complaint (naming conventions not always aligning well) can be completely fixed with macros if you want (there are dozens of such projects).

xvilka|4 years ago

If one wants a "fixed" CL they could look at Scheme. Sadly, that language didn't become even as popular as CL.

hayley-patton|4 years ago

You can't seriously say that, just because one targets a well-specified machine, that the language being used is well-specified. The determinism of a Clojure-on-JVM program would also be dependent on the particular code the Clojure compiler generates. The code has to remain semantically identical between compiler versions, and it should not introduce any new non-determinism, e.g. that presented in [1]. In Common Lisp there is the Armed Bear Common Lisp implementation, which runs on a JVM. Does it benefit from JVM determinism or not? It probably does not, because the JVM is simply not aware of undefined behaviour that ABCL or Clojure are implicitly defining.

When it comes to having different platforms, it would also be necessary for any other compilers to generate semantically identical code. Different Clojure systems do _not_ do that. For example, arithmetic in ClojureScript uses JS floats where Clojure-on-JVM and others use integers of some size.

In my experience, writing a non-conforming CL program is hard, and much harder than writing a program without undefined behaviour in C. I am not sure why, other than the UB being more "localised" in some vague way. But there is also a modification of the ANSI standard being worked on, which attempts to eliminate undefined behaviour <https://github.com/s-expressionists/wscl>.

[1] Hans Boehm, Threads cannot be implemented as a library <https://web.stanford.edu/class/cs240/readings/p261-boehm.pdf>

pjmlp|4 years ago

Kind of, there is some fun tracking down JVM specific implementation behaviours.

JIT, GC and extension differences do give space enough for head scratching, although not as much in other ecosystems.

dandotway|4 years ago

If you have a second, I'm curious to learn more about your head scratching experiences with JVM. I want to make a program that I can trust will still run the exact same (bug-for-bug compatible) many years in the future without maintenance. One approach is to make it completely bug-free using a formal verifier for a strict formalization of C, but this is extraordinary effort and there is no guarantee that bugs in the stack of garbage my app sits atop and the libraries I call (SDL2?) will cause unwanted user observable behavior. Truly bug-free is actually stricter than what I really need; I just need exact bug-for-bug compatibility so that my bugs always are deterministic. It seems with the JVM at least, the bug-for-bug determinism is really good (except when it obviously isn't and can't be, like thread scheduling, network communications, ...). For client GC there is a low-latency guarantee and people seem happy. Have you found the Java GC is not all it's reputed to be? There are so many huge companies with billions invested in Java and its bug-for-bug compatibility, I think it could easily still be around in a 100 years along with COBOL and is a safe investment for individuals who value longevity above what is trendiest and shiniest.

xvilka|4 years ago

Portability of JVM is a myth. There are many small differences between implementations, platforms, toolkits. Moreover, while Java language continues to improve the syntax, many projects stuck with the old versions of it, and seem not in hurry to upgrade, thus preventing the use of them.

pixelrevision|4 years ago

Clojure seems like the best choice if you want to use lisp in a professional context but is a bit of a double edged sword if learning lisp for exploratory purposes. When things go wrong with Clojure you get back confusing stack traces from the JVM and you have to work through 2 separate paradigms to figure them out. Common Lisp on the other hand has a baked in live interactive environment that lets you work through errors as they come up. This is such a unique (and productive) way to work that it’s addictive. That said Clojure’s api is just way more intuitive, probably due to when it was created.