In Portugal, Siscog used to be a Lisp shop, no idea nowadays.
Then you have the Clojure based companies, where Datomic and Nubank are two well known ones, even if not a proper Lisp, still belongs to the same linage.
Lisp languages are niche, but frequently used as seen in the great projects mentioned in this thread. Since 1982, I have been employed about 20% of my time using mostly Common Lisp and for a few years Clojure. Racket is a great language and system for learning and having fun, so, have fun!
*I am learning scheme(dr racket), which is i think derived from lisp*
it _is_ Lisp. Namely lisp-1, vs what one would consider lisp like common lisp would be lisp-2. Difference mostly being that in lisp-1 everything's in single namespace, whereas lisp-2 has more. So, in scheme you cannot have a function and a variable have the same name. In common lisp you can. Other diffs being (syntactically) passing functions and executing them. There are other things, of course, but not that big of a deal. Scheme is simpler and suitable for teaching / getting into lispen. I'd argue it might also be a rather well-equipped DSL.
Scheme is mostly used for teaching, but there are many production applications out there written in Lisp (Emacs for example). Also I'd like to mention Clojure, which is "lispy" and used by big cooperations.
Current racket is running on top of chez scheme - which is maintained by Cisco - and reportedly extensively used in commercial products (router firmware/os etc).
It was brought into Cisco to do that but the project was eventually shelved, which was a shame because the prototypes delivered some really interesting reliability features. Most Cisco hardware products run firmware written in C. Management systems are often Java and (increasingly) Go. Clojure is used for one of the security product lines, but that was developed as a startup that was later purchased by Cisco. One of the management systems, NSO, is written in Erlang (brought in through the tail-f acquisition). There are certainly a lot of people in Cisco that understand the power of Lisp (I was one), but they are spread out and surrounded by people that just want to push whatever the latest thing is (now Go). C.f. the blub paradox and “worse is better.” They have a lot of legacy code that was written over the last 30 years that powers their devices, and that’s all in C.
Not sure what they are using now, but fairly recently the folks over at grammarly were still using Common Lisp (CCL and SBCL) among other technologies:
Some companies: https://github.com/azzamsa/awesome-lisp-companies/ (Routific, Google's ITA Software, SISCOG running resource planning in transportation, trading, big data analysis, cloud-to-cloud services, open-source tools (pgloader, re-written from Python), games (Kandria, on Steam and GOG, runs on the Switch), music composition software and apps…
Often as a DSL (domain specific language) for extending applications at runtime and/or configuration. I wouldn't start a "serious" project in Lisp today; meaning, a project with investment behind it, but Lisp can be a real joy to work with, and I've used Clojure for countless hobby projects. Clojure, in particular, has lots of deployments around the tech industry, and it's the foundation of the Jepsen DB test suite, Datomic (an immutable DB), and Metabase, as a few examples. Walmart has a non-trivial amount of Clojure running in prod as well.
I used a few different lisps for pet projects and honestly today for me the biggest problem of lisps is the typing. ADTs (and similar systems) are just super helpful when it comes to long term development, multiple people working on code, big projects or projects with multiple pieces (like frontend+backend) and it helps AI tools as well.
And this in not something lisps explored much (is there anything at all apart from Racket/typed dialect?), probably due to their dynamic nature. And this is why I dropped lisps in favour of Rust and Typescript.
SBCL has fine type checking. Some is done at compile time -- you get warnings if something clearly can't be type correct -- but otherwise when compiled with safety 3 (which people tend to make the default) types are checked dynamically at run time. You don't get the program crashing from mistyping as one would in C.
> You don't get the program crashing from mistyping as one would in C.
Sorry but I don't compare to C anymore, I want the same safety as in Rust or Typescript: exhaustive checks, control-flow type narrowing, mapped types and so on. Some detection at compile time is not enough, since there is a way to eliminate all type errors I want to eliminate them all, not some.
Why stop there? Why not demand proof of correctness? After all, that's now within reach using LLMs producing the formal specification from a simple prompt, right?
SBCL does a fine job in detecting type mismatches within the frame of ANSI Common Lisp, not Haskell. While I would agree that a strict type system eases long term maintenance of large systems, for "explorative computing", proof-of-concepts, RAD or similar that tends to get in the way. And if such proof-of-concept looks promising, then there is no shame in rewriting it in a language more suitable for scale and maintenance.
Proof of correctness would be fantastic, but I have yet to see it in action. LLMs maybe could do it for simple program, but I'm pretty sure it will fail in large codebases (due to context limits), and types help a lot in that case.
> for "explorative computing", proof-of-concepts, RAD or similar that tends to get in the way
I would even argue that its better to have typed system even for POCs, because things change fast and it very often leads to type errors that need to be discovered. At least when I did that I often would do manual tests after changes just to check if things work, with typing in place this time can also be minimised.
> You don't get the program crashing from mistyping as one would in C.
Uh, isn't that exactly what happens with runtime type checking? Otherwise what can you do if you detect a type error at runtime other than crash?
In C the compiler tries to detect all type errors at compile time, and if you do manage to fool the compiler into compiling badly typed code, it won't necessarily crash, it'll be undefined behavior (which includes crashing but can also do worse).
> Uh, isn't that exactly what happens with runtime type checking?
No, it raises an exception, which you can handle. In some cases one can even resume via restarts. This is versus C, where a miscast pointer can cause memory corruption.
Again, a proper C compiler in combination with sensible coding standards should prevent "miscast pointers" at compile time / static analysis. Anyway, being better than C at typing / memory safety, is a very low bar to pass.
I'm curious in what situation catching a typing exception would be useful though. The practice of catching exceptions due to bugs seems silly to me. What's the point of restarting the app if it's buggy?
Likewise, trying to catch exceptions due to for example dividing by zero is a strange practice. Instead check your inputs and throw an "invalid input" exception, because exceptions are really only sensible for invalid user input, or external state being wrong (unreadable input file, network failures, etc.).
If "just don't do the bad things" is a valid argument, why do we need type checking at all?
Exceptions from type checking are useful because they tell you exactly where something has screwed up, making fixing the bug easier. It also means problems are reduced from RCEs to just denial of service. And I find (in my testing) that it enables such things as rapid automated reduction of inputs that stimulate such bugs. For example, the SBCL compiler is such that it should never throw an exception even on invalid code, so when it does so one can automatically prune down a lambda expession passed to the COMPILE function to find a minimal compiler bug causing input. This also greatly simplifies debugging.
A general reason I look down on static type checking is that it's inadequate. It finds only a subset, and arguably a not very interesting subset, of bugs in programs. The larger set of possible bugs still has to be tested for, and for a sufficient testing procedure for that larger set you'll stimulate the typing bugs as well.
So, yeah, if you're in an environment were you can't test adequately, static typing can act as a bit of a crutch. But your program will still suck, even if it compiles.
The best argument for static typing IMO is that it acts as a kind of documentation.
That's very silly, because in languages that do static type checking right, like Haskell, Rust, or to a lesser extent TypeScript, if you have a typing bug your program won't even compile. How's that "just don't do bad stuff"?
> It finds only a subset, and arguably a not very interesting subset, of bugs in programs. The larger set of possible bugs still has to be tested for, and for a sufficient testing procedure for that larger set you'll stimulate the typing bugs as well.
I agree that having your program be properly typed is a minumum requirement for it to even be possibly correct. So why would you not check that requirement statically? Skipping it is akin to not checking the syntax at compile time, instead preferring to crash at runtime due to invalid syntax. Or worse, throwing an exception. And then you're supposed to catch the "missing parenthesis exception"?
If you're building a bridge, presumably you'll calculate the forces and everything, before putting it together and verifying it actually holds for those forces. Likewise, if I specify that my function f returns a positive integer, why would I not let the compiler check that assumption statically? Are you really saying you'll write a test like (assert (> (f) 0)) or whatever? Seems like a huge waste of time building the app and starting up the test suite, when you could just as well just have stopped the build when the compiler saw you were returning the wrong type.
> For example, the SBCL compiler is such that it should never throw an exception even on invalid code
What does this even mean? What does it do if you run (/ 1 x) where x = 0?
> So, yeah, if you're in an environment were you can't test adequately, static typing can act as a bit of a crutch. But your program will still suck, even if it compiles.
This is a ridiculous argument, and is much closer to "just don't do the bad things" than what I said. Also note that I never said "don't write tests". You're making a complete strawman by pitting type checking against testing. It's two complimentary methods of verifying correctness, and the type system is a quicker and more accurate way of catching bugs, so moving all logic that you possibly can to the type system and away from tests is a huge win in efficiency and correctness. That doesn't mean skip testing, just like checking the syntax of your program statically doesn't mean skip testing.
Just because I'm wearing a seat belt (type checking) doesn't mean I'll drive recklessly (skip testing). Likewise, just being a careful driver (writing tests) doesn't mean you should stop wearing your seat belt (type checking).
You can run Coalton on Common Lisp. It has a type system similar to Haskell’s. And interops very well with pure Common Lisp. It also modernizes function and type names in the process so it makes Lisp more familiar to modern developers. I tried it in a small project and was impressed.
Thanks everyone for opening my mind, actually it is being taught in a core course at my college, and it is very different course form others that i have taken. course teaches feature of lisp how they are unique and useful. Also we are solving simple questions like Fibonacci, or number patterns, list pattern, recursion vs iterative etc.
reply