Wow, wasn't expecting to see my post on here! Eventually, I want to write a follow-up, but I'm still a beginner.
Here's what I've liked about Common Lisp so far:
* The condition system is neat and I've never used anything like it -- you can easily control code from afar with restarts
* REPL-driven programming is handy in situations where you don't quite know what will happen and don't want to lose context -- for example parsing data from a source you're unfamiliar with, you can just update your code and continue on instead of having to save, possibly compile, and restart from the very beginning
* Common Lisp has a lot of implementations and there's a good deal of interoperability -- I was able to swap out implementations to trade speed (SBCL) for memory usage (CLISP) in one case (multiple compatible implementations is one of the reasons I've been leaning towards CL instead of Scheme for learning a Lisp)
* Even as an Emacs noob, the integration with Common Lisp is excellent, and it works great even on my super slow netbook where I've been developing -- this isn't as big of an advantage these days with fast computers, VS Code, and language servers, but it's definitely retrofuturistic
There's also a few things I don't like:
* The most popular package manager (QuickLisp) is nice, but not nearly as featureful as I've become accustomed to with newer languages/ecosystems
* Since the language itself is frozen in time, you need lots of interoperability libraries for threads, synchronization, command line arguments, and tons of other things
* I really, really wish SBCL could support fully static builds, to enable distributing binaries to non-glibc Linux distributions
I'm sure there are more pros/cons, but that's what came to mind just now.
Last time I checked on it, QuickLisp doesn't support fetching packages over anything except for plain http, with no encryption and no verification mechanism in place to detect files that may have been tampered with during transmission.
I think not supporting encryption or authentication for something as important as fetching source code makes QL a non-starter for me and hopefully for anyone else who cares about security.
Another issue I have ran into, is that SBCL is hosted on sourceforge, which has in the past injected malware into projects downloadable archives! I consider this to also be a security issue, and sourceforge in general is not pleasant to work with. I don't think there are any valid reasons to continue to use sourceforge today, so why such an important project continues to use it confuses me a lot.
I don't see these issues mentioned by anyone else which is bizarre to me.
I really like lisps and common lisp specifically but things like this has driven me away from using it and it doesn't appear that anyone cares about fixing these things.
These issues get mentioned a lot, you just haven't noticed I guess. Sourceforge is also an issue with some C libraries too, I'm guessing because it was done a long time ago? not sure.
I use ECL because it has really good C interop. It actually lets you inline C and access macros directly, making it a great glue language for C libraries. It's what I'm using it for now. I think you might even be able to avoid the GC entirely and use it to script C programs together in a performant way, by using the C FFI to allocate and manage the memory, including the ECL types, instead of the GC. And that's actually doable because of how good the inspector/debugger for lisp is. You can even inline assembly. I'm working on a bunch of CL stuff around this sort of thing, I plan to do a writeup of it and share it once I've developed it more.
Lisp has it's downsides, but the C FFI/embeddability, along with the excellent low-level debugger/inspector, interactivity, and conditions and restarts, makes it worth the time for me to invest in it. And the stability of the language. My main gripe is the reader, but it's easy-ish enough to avoid the problems with named-readtables, or a simple lisp parser for `read` or whatever. I like Clojure, but it's missing some key stuff from the old lisp world that I'd love to see. Shadow-cljs is awesome.
Nix has a really convenient new CL libraries packaging upstream now. That verifies everything with sha256. It's quite complete because it's seeded from Quicklisp and had more packages added on (aswell as their native library dependcies.)
Nix isn't to everyone's taste but it demonstrates that you can treat security/reproducibility/etc as orthogonal to Quicklisp and Sourceforge (and to Lisp native tooling in general.)
> Last time I checked on it, QuickLisp doesn't support fetching packages over anything except for plain http, with no encryption and no verification mechanism in place to detect files that may have been tampered with during transmission.
I know it's not an excuse, but it was fun as heck booting up "capital M" MacOS (9.2.1) and loading Quicklisp into MCL without any trouble. I'm not even sure that's a supported platform by Quicklisp. https://code.google.com/archive/p/mcl/
When I started using CL 20 years ago, libraries were stored on cliki and any malicious user could put malware there. Any source you asdf-installed was generally GPG signed and the installer automatically checked signatures against your personal trust-chain.
Learning CL back then was my first introduction to GPG (and Emacs, and Linux)
For static builds, if you're willing to run a slightly older version of sbcl daewok's work on building and linking sbcl in a musl environment might be solution you're looking for. I've tried to port his patches to more recent versions but there are segfaults due to changes in upstream.
I’ll take a look, thanks! My biggest concern with Scheme is that each implementation seems to have its own ecosystem due to subtle incompatibilities.
From an outsider’s perspective it seems a lot more fragmented than CL. Not necessarily a big deal if you have the libraries you want, but it gives me pause.
That's true. For cases when you want to start with a good set of libraries (json, csv, databases, HTTP client, CLI args, language extensions…), I am putting up this collection together: https://github.com/ciel-lang/CIEL/ It can be used as a normal Quicklisp library, or as a core image (it then starts up instantly) or as a binary.
It can run scripts nearly instantly too (so it isn't unlike Babashka). We are ironing out the details, not at v1.0 yet.
> handling a runtime error by just fixing the broken code--in-place, without any restarts [from the blog]
We run a long and intensive computation and, bad luck, we get an error in the last step. Instead of re-running everything again from zero, we get the interactive debugger, we go to the erroneous line, we compile the fixed function, we come back to the debugger, we choose a point on the stackframe to resume execution from (the last step), and we see our program pass. Hope this illustrates the feature well!
I like your pragmatic approach of using Lisp where it makes sense and not being afraid to shell out to something else where appropriate (among many other nuggets of wisdom).
I cdr car less about your cons. Seriously though, mad props for being diligent enough to spend your attention on this. There is a lot to learn from people who came before us and build on that.
I just create new / changed functions next to the others and eval the selected region, then clean up. When I think i'm done, I'll restart the repl and try if it all is fine or if I depended on something in the state. That doesn't often happen anymore. I use the repl to try out things I just written in files. I can't say I remember a moment when state was a/the problem.
I use Clojure at work but wow do I miss just about everything about Common Lisp whenever I have to debug anything or want performant code. Being able to be in nested errors and click at any part of the stack to inspect lexical bindings is extremely useful, and more importantly, clicking on an object then pushing M-<RET> to copy it to my REPL is much nicer than what Clojure offers (tap>, which I consider a glorified pretty printer even if you use tools like Portal).
As for performance, well, Common Lisp lets you statically type things, and SBCL can emit really efficient code if you do this. I find it helpful to run DISASSEMBLE on my own code to see what exactly is being emitted and optimize from there. And more importantly, packages like SB-SIMD and Loopus are a god send for any number crunching application.
This nicely summarizes some of my frustrations with using Clojure for my master's thesis. I'm not unhappy with the choice. Clojure allows such a juicy crossover between "everything is a key-value map, mannn" and "If it has :quack key set to true, treat it like a duck" which works really well for entity-component-system game-design-y things.
but the development story in Common Lisp ... and my gawd, the CONDITION SYSTEM ... were things that I sorely missed for the last year. and I'm not even that experienced of a CL hacker. It just grew on me so quickly. If only CLOS and the primitive data types in CL played together more nicely than they seem to.
I know. I've been spending a lot of time with CL, Scheme, and Clojure the past few years, and the ideal Lisp is some combination of them all. There are aspects of each that I miss in the others. CL has the nicest environment and development story (generally speaking). Scheme feels more refined in the small. And although they can be divisive, I really appreciate Clojure's data structure literals.
Steel Bank Common Lisp is the workhorse which led me to build profitable software companies. I don't think I would be as productive without it. The repl driven workflow is amazing and the lisp images are rock solid and highly performant.
It looks awesome, but I'm too lazy as of today to go back to Emacs. I usually just use VSCode close to the defaults for my (mostly) Python and JavaScript development. I don't code full time, since I'm on a CTO role.
You may be interested in https://github.com/nobody-famous/alive which brings the power of slime to vscode (Mostly, it's relatively new and missing some features, but getting better all the time)
I don't know if you're interested in Sublime Text or not but https://github.com/s-clerc/slyblime is pretty good. VS Code also has Alive which I heard is good although I don't use Electron apps.
I think the times when your tech stacks mattered in the slightest are mostly behind us.
Also: it's good you concocted some arcane shit that works like a charm, but now nobody - except the ones whose pay you express in number of zeroes - is touching it.
I think that you’re just restating the Blub point of view. You look back on the tech stacks of the past, and can see how they were worse than the ones we have today, but looking at the ones today you think that there are no more improvements to be made — or at least, none that matter.
Given that (I assume) you really do appreciate how much better the stacks of today are then the ones of the past, that seems a highly unwarranted assumption. Heck, I will tell you this: as much as a Lisp stack is better than the alternatives today, it’s not perfect. There’s a ton of future work to improve things even above the current state of the art.
But that state of the art is still better than what everyone else is using. What’s great about Lisp is that improvements are possible: with other technologies, there are more hard limits on what can be done.
Of course people "realise" this. But those REPLs are not actually REPLs. They are interactive language prompts. They aren't actually REPLs. As the joke goes, Python doesn't have a REPL: it lacks READ, EVAL, PRINT and LOOP.
Being able to type in code and have it evaluated one line at a time isn't a REPL.
The repls you mention are not like lisp repls. You're being downvoted because your comment makes it sound like you've never programmed a lisp but have strong opinions nonetheless.
A REPL isn't just a REPL. You are comparing modern day Toyota Corollas to a Spaceship sent from the future to the 80s. One is just different level radical.
At least when it's baked by SLY or SLIME
As a Clojure dev, break loops and REPL-driven workflows sound wonderful, and something we could definitely benefit from, which would make it more like front-end coding with JS/TypeScript using the browser’s awesome debugging tools. Sadly, the state of tooling and community support for the Clojure ecosystem seems to be pretty lackluster at present.
Clojure can kinda-sorta simulate the true REPL workflow, if you're making something like a web server where deep calls down the code hierarchy only happen with each request. So you can rewrite and reload various functions while the server is still running and make requests from your browser again. The caveat is that eventually these redefinitions and overwrites pollute the namespaces and eventually something will break, at which point you reload your server.
I love CL and I really miss it when i'm doing something else. I mean, many things are such a pain in 'modern' languages that it's not even funny, when you compare it to the Lisp experience of even decades ago.
There are many cons, but those are simply not as bad as most of the pure technical language/dev env cons in almost everything else. Sure Python & JS have more uptake, more libraries etc, but the experience of developing for them is so much worse. IMHO of course. I have been doing a lot of languages over the years including C#, TS, Py, Hs and more esoteric ones, but I keep coming back to CL (SBCL + emacs + Slime) when I get seriously angry about stuff that is missing or plainly bad in those languages. It makes me relaxed and convinced there is some good in the world after all.
I am currently raising for a product we (foolishly so) bootstrapped in Typescript but now we will, for a launch version, redo it in CL. Meaning I get to work with / in CL (and all of the fun stuff; implementing DSL, code generation, working with macros, implementing a static type solver etc) for the coming 3-5 years before we launch. Lovely.
1) painless debugging with Emacs/Slime vs Rider/VS/VSCode
2) performance & consistency of it; SBCL is fast and remains that way, I can leave emacs/slime running for months and it doesn't degrade; vs/rider... they really burn a hole through my laptop and isn't even that good at most things compared, even on really old computers
3) Re-eval; you can re-eval a region, function, file, last expression etc, during running and/or debugging, which is very flexible ; something wrong, change (or write the same function next to it with a fix; very handy), re-eval and when happy , clean up and save
4) data formats... We have had, since 'forever' had 'proxy' tooling that converts any incoming and outgoing JSON to and from Lisp lists. While in code, we only work with s-expressions and it makes life so easy.
I see a lot of “coding” talk in the blog and comments from the author here, but few mentions as to what kind of software they’re building or what use cases they’re targeting.
My hot take is that the reason functional programming never took off is that, while it certainly is fine for writing programs, most software these days is not “program running locally on my pc/server from the command line until it completes” and is instead “program that starts, reacts to input from user, then gets closed by the user” or “program that starts, then responds to network or other automated I/O (to serve web pages, to monitor something, to emit logs, etc) then stops when the other software tells it to”. This is a lot harder to do in a purely functional style, or at least it is in most opinionated functional programming implementations I’ve used, because you’re no longer “just” evaluating some expression but instead initializing state, reacting to I/O, then updating state and/or performing further I/O potentially while using parallelization to perform monitoring/listen for other things/perform further I/O and state updates.
Of course it’s not impossible to do these things with Lisp but from my couple of semesters of exposure of FP in undergrad and use of FP features in C++ and Scala professionally to solve these kinds of problems… it seems quite hard to get FP to work for these applications, and that lack of suitability is what discourages me from diving more fully into FP
> I see a lot of “coding” talk in the blog and comments from the author here, but few mentions as to what kind of software they’re building or what use cases they’re targeting.
Good point! This is all currently just a hobby. For Common Lisp specifically, the only things I've produced are a (mediocre) Battlesnake client and a (now defunct, as of yesterday) multiplayer word scramble game. Neither of these really derives much benefit from being created in Lisp, but I learned a lot along the way (which was really the point).
Unrelated to Common Lisp, I've found myself often needing to write code that generates code. This is an area where I suspect Lisp will shine, although I haven't had a chance to give it a try yet. Two examples from recent projects (which I tackled before ever thinking about using Common Lisp) are:
* Generating code to validate a particular JSON Schema (used in a static site generator)
* Generating JSX from Markdown (used for story content in a programming game)
To say nothing of the innumerable C macros I've written in my lifetime :)
Thanks for the answer! Your word scramble game in particular seems like something that approximates my “maybe not a good fit for FP” bucket. Do you plan on sharing it on GitHub or describing the challenges you ran into?
Completely agree code generation is where I expect Lisp to perform the best. Though, I looked it up and apparently Markdown is not context-free so.. curious as to the challenges that introduces as I figure FP could zap through parsing a CFG but really struggle with state for something not context free
Note that Common Lisp contains CLOS, which is one of the most advanced object-oriented systems even now. Most Lisps are not functional like Haskell is.
Yes, the article calls it powerful, but aside from the ability to update classes and their functions at runtime - which, maybe I’m missing the utility of so I won’t say it’s useless although in my experience a SharedInstanceSingleton or LocalImmutableConfig or BatchedMonitoringEvent wouldn’t need it - it’s “just” dynamically resolving the method implementation to use based on argument types dynamically and letting you provide an order of precedence for diamond inheritance.
I think it does solve one problem I have - with some solid tooling and a guarantee that class updates at runtime don’t disrupt ongoing calls, it might let me patch running binaries without doing a full update. Though, the cost is that I seem to incur a few extra lookups or even sorts on each dynamically dispatched method, to resolve the implementation? Besides that it doesn’t seem to really solve most problems I have regarding I/O or state - having the option to update some of these at runtime is interesting but it seems like something I’d only want to selectively enable
Elixir uses functional programming and its excellent for web development and whenever you want a fault tolerant system.
You also don't need to throw out all the good features of the other styles, as parts of functional programming are becoming more and more common in "regular" languages too. Rust uses functional patterns in many cases for instance.
And you can also write Lisp in an OO or imperative style if you want, it's no Haskell.
It took me a while to grok monads, and the IO monad, and longer still to figure out how to compose them in safe ways, and manipulate execution order, etc. But: now I can write typesafe applications, and I produce fewer bugs when I work in non-FP languages (I get paid to write Java.) Lisp is a starting point. Haskell is where it's at. I recommend learning the style, even if you never produce production code in it.
Yes, Haskell is magnificent for learning FP. I used to think Haskell was terrible for IO, but my tune has changed dramatically since I started working with it full time.
Let’s say I want to do something simple but slightly beyond the scope of a traditional toy demonstration:
* Read some environment variables and a local file
* Start a monitoring thread that consumes from a channel or something similar, then every X s or X events writes to a local temp file and then sends a request batching some metrics to an external system
* Configure and start an http server
* Said server has a handler that 0. Starts a timer 1. loads, then increments an atomic “num requests served until now” variable 2. uses synchronization to lock on a list or ring buffer containing the last 5 requests’ user-agent headers 2.5 copies the current last 5 values, replaces oldest one with the one from the handles request, unlocks 3. generates a json response containing like “num_so_far: x, last5agent: [..], “some_env_var”:..” 3.5 stops the timer 4. write request user agent and time interval to monitoring thread’s channel 5. write response and end handling
* server’s gotta be able to do concurrency > 1 with parallelism
* On sigterm set the server to a state that rejects new requests, waits for existing requests to complete, then flushes the monitoring channel
I’d consider this a trial run of some of the most basic patterns commonly used by networked software: init io, immutable shared state, atomic mutable shared state, synchronization locked shared state, http ingress and egress, serialization, concurrency, parallelizarion, background threads, os signals, nontrivial cleanup. In Go, Java, or C++ I could write this with my eyes closed. How easy is it in Haskell or Lisp?
If you know of any demos or repos that do something like this - not a pure toy or barebones demo, but not a huge task all in all- in either I’d be interested in seeing what it looks like.
I'm using Lisp for simulation. It's really wonderful being able to poke and prod long-running computations while they run. I missed this too much when I tried using Julia.
When I want "this will run forever," I write it in Rust.
When I want "this will compile forever," I write it in ANSI C.
When I want "this will live forever," I write it Python 2.7 and make it the backbone of the entire org's infra templating. Bonus points if it's a custom Ansible module.
Here's what I've liked about Common Lisp so far:
* The condition system is neat and I've never used anything like it -- you can easily control code from afar with restarts
* REPL-driven programming is handy in situations where you don't quite know what will happen and don't want to lose context -- for example parsing data from a source you're unfamiliar with, you can just update your code and continue on instead of having to save, possibly compile, and restart from the very beginning
* Common Lisp has a lot of implementations and there's a good deal of interoperability -- I was able to swap out implementations to trade speed (SBCL) for memory usage (CLISP) in one case (multiple compatible implementations is one of the reasons I've been leaning towards CL instead of Scheme for learning a Lisp)
* Even as an Emacs noob, the integration with Common Lisp is excellent, and it works great even on my super slow netbook where I've been developing -- this isn't as big of an advantage these days with fast computers, VS Code, and language servers, but it's definitely retrofuturistic
There's also a few things I don't like:
* The most popular package manager (QuickLisp) is nice, but not nearly as featureful as I've become accustomed to with newer languages/ecosystems
* Since the language itself is frozen in time, you need lots of interoperability libraries for threads, synchronization, command line arguments, and tons of other things
* I really, really wish SBCL could support fully static builds, to enable distributing binaries to non-glibc Linux distributions
I'm sure there are more pros/cons, but that's what came to mind just now.
Last time I checked on it, QuickLisp doesn't support fetching packages over anything except for plain http, with no encryption and no verification mechanism in place to detect files that may have been tampered with during transmission.
I think not supporting encryption or authentication for something as important as fetching source code makes QL a non-starter for me and hopefully for anyone else who cares about security.
Another issue I have ran into, is that SBCL is hosted on sourceforge, which has in the past injected malware into projects downloadable archives! I consider this to also be a security issue, and sourceforge in general is not pleasant to work with. I don't think there are any valid reasons to continue to use sourceforge today, so why such an important project continues to use it confuses me a lot.
I don't see these issues mentioned by anyone else which is bizarre to me.
I really like lisps and common lisp specifically but things like this has driven me away from using it and it doesn't appear that anyone cares about fixing these things.
- add in https://github.com/rudolfochrist/ql-https (downloads packages with curl)
- use another package manager, CLPM: https://www.clpm.dev (or the newest ocicl)
> CLPM comes as a pre-built binary, supports HTTPS by default, supports installing multiple package versions, supports versioned systems, and more.
- use mitmproxy: https://hiphish.github.io/blog/2022/03/19/securing-quicklisp...
I use ECL because it has really good C interop. It actually lets you inline C and access macros directly, making it a great glue language for C libraries. It's what I'm using it for now. I think you might even be able to avoid the GC entirely and use it to script C programs together in a performant way, by using the C FFI to allocate and manage the memory, including the ECL types, instead of the GC. And that's actually doable because of how good the inspector/debugger for lisp is. You can even inline assembly. I'm working on a bunch of CL stuff around this sort of thing, I plan to do a writeup of it and share it once I've developed it more.
Lisp has it's downsides, but the C FFI/embeddability, along with the excellent low-level debugger/inspector, interactivity, and conditions and restarts, makes it worth the time for me to invest in it. And the stability of the language. My main gripe is the reader, but it's easy-ish enough to avoid the problems with named-readtables, or a simple lisp parser for `read` or whatever. I like Clojure, but it's missing some key stuff from the old lisp world that I'd love to see. Shadow-cljs is awesome.
Nix isn't to everyone's taste but it demonstrates that you can treat security/reproducibility/etc as orthogonal to Quicklisp and Sourceforge (and to Lisp native tooling in general.)
That would be unbelievably irresponsible. Has this really not been addressed by the CL community?
Edit: here’s the issue: https://github.com/quicklisp/quicklisp-client/issues/167
Thanks for bringing this up!
I know it's not an excuse, but it was fun as heck booting up "capital M" MacOS (9.2.1) and loading Quicklisp into MCL without any trouble. I'm not even sure that's a supported platform by Quicklisp. https://code.google.com/archive/p/mcl/
I’m sure you do :°)
Learning CL back then was my first introduction to GPG (and Emacs, and Linux)
https://www.timmons.dev/posts/static-executables-with-sbcl.h...https://www.timmons.dev/posts/static-executables-with-sbcl-v...
From an outsider’s perspective it seems a lot more fragmented than CL. Not necessarily a big deal if you have the libraries you want, but it gives me pause.
That's true. For cases when you want to start with a good set of libraries (json, csv, databases, HTTP client, CLI args, language extensions…), I am putting up this collection together: https://github.com/ciel-lang/CIEL/ It can be used as a normal Quicklisp library, or as a core image (it then starts up instantly) or as a binary.
It can run scripts nearly instantly too (so it isn't unlike Babashka). We are ironing out the details, not at v1.0 yet.
> handling a runtime error by just fixing the broken code--in-place, without any restarts [from the blog]
Also (second shameless plug) I should have illustrated this here: https://www.youtube.com/watch?v=jBBS4FeY7XM
We run a long and intensive computation and, bad luck, we get an error in the last step. Instead of re-running everything again from zero, we get the interactive debugger, we go to the erroneous line, we compile the fixed function, we come back to the debugger, we choose a point on the stackframe to resume execution from (the last step), and we see our program pass. Hope this illustrates the feature well!
I like your pragmatic approach of using Lisp where it makes sense and not being afraid to shell out to something else where appropriate (among many other nuggets of wisdom).
But REPL development is a mixed blessing. There are many situations where you want to start from a blank slate with no previous state.
LISP would be a more practical language if it included a trivial option to make that possible.
If you're using SLIME: M-x restart-inferior-lisp
M-x slime-restart-inferior-lisp
works fine.
As for performance, well, Common Lisp lets you statically type things, and SBCL can emit really efficient code if you do this. I find it helpful to run DISASSEMBLE on my own code to see what exactly is being emitted and optimize from there. And more importantly, packages like SB-SIMD and Loopus are a god send for any number crunching application.
but the development story in Common Lisp ... and my gawd, the CONDITION SYSTEM ... were things that I sorely missed for the last year. and I'm not even that experienced of a CL hacker. It just grew on me so quickly. If only CLOS and the primitive data types in CL played together more nicely than they seem to.
Deleted Comment
This is certainly easy to do with Cider and I imagine the main tooling in other editors is equally competent.
There are some helper projects like https://github.com/Bronsa/tools.decompiler, and on the OpenJDK JitWatch (https://github.com/AdoptOpenJDK/jitwatch), other JVMs have similar tools as well.
It isn't as straightforward as in Lisp, but it is nonetheless doable.
Deleted Comment
Also: it's good you concocted some arcane shit that works like a charm, but now nobody - except the ones whose pay you express in number of zeroes - is touching it.
Given that (I assume) you really do appreciate how much better the stacks of today are then the ones of the past, that seems a highly unwarranted assumption. Heck, I will tell you this: as much as a Lisp stack is better than the alternatives today, it’s not perfect. There’s a ton of future work to improve things even above the current state of the art.
But that state of the art is still better than what everyone else is using. What’s great about Lisp is that improvements are possible: with other technologies, there are more hard limits on what can be done.
do people not realize that basically everything vm/interpreted language has a repl these days?
https://www.digitalocean.com/community/tutorials/java-repl-j...
https://github.com/waf/CSharpRepl
https://pub.dev/packages/interactive
not to mention ruby, python, php, lua
hell even c++ has a janky repl https://github.com/root-project/cling
edit: i get downvoted by the lisp crowd every time i bring up that the repl isn't a differentiating feature anymore :shrug:
Being able to type in code and have it evaluated one line at a time isn't a REPL.
I’d suggest it has more to do with tone than content.
https://github.com/kaveh808/kons-9
There are many cons, but those are simply not as bad as most of the pure technical language/dev env cons in almost everything else. Sure Python & JS have more uptake, more libraries etc, but the experience of developing for them is so much worse. IMHO of course. I have been doing a lot of languages over the years including C#, TS, Py, Hs and more esoteric ones, but I keep coming back to CL (SBCL + emacs + Slime) when I get seriously angry about stuff that is missing or plainly bad in those languages. It makes me relaxed and convinced there is some good in the world after all.
I am currently raising for a product we (foolishly so) bootstrapped in Typescript but now we will, for a launch version, redo it in CL. Meaning I get to work with / in CL (and all of the fun stuff; implementing DSL, code generation, working with macros, implementing a static type solver etc) for the coming 3-5 years before we launch. Lovely.
Can't have Lisp without cons. (sorry)
What do you miss in Lisp when working in C#?
1) painless debugging with Emacs/Slime vs Rider/VS/VSCode
2) performance & consistency of it; SBCL is fast and remains that way, I can leave emacs/slime running for months and it doesn't degrade; vs/rider... they really burn a hole through my laptop and isn't even that good at most things compared, even on really old computers
3) Re-eval; you can re-eval a region, function, file, last expression etc, during running and/or debugging, which is very flexible ; something wrong, change (or write the same function next to it with a fix; very handy), re-eval and when happy , clean up and save
4) data formats... We have had, since 'forever' had 'proxy' tooling that converts any incoming and outgoing JSON to and from Lisp lists. While in code, we only work with s-expressions and it makes life so easy.
My hot take is that the reason functional programming never took off is that, while it certainly is fine for writing programs, most software these days is not “program running locally on my pc/server from the command line until it completes” and is instead “program that starts, reacts to input from user, then gets closed by the user” or “program that starts, then responds to network or other automated I/O (to serve web pages, to monitor something, to emit logs, etc) then stops when the other software tells it to”. This is a lot harder to do in a purely functional style, or at least it is in most opinionated functional programming implementations I’ve used, because you’re no longer “just” evaluating some expression but instead initializing state, reacting to I/O, then updating state and/or performing further I/O potentially while using parallelization to perform monitoring/listen for other things/perform further I/O and state updates.
Of course it’s not impossible to do these things with Lisp but from my couple of semesters of exposure of FP in undergrad and use of FP features in C++ and Scala professionally to solve these kinds of problems… it seems quite hard to get FP to work for these applications, and that lack of suitability is what discourages me from diving more fully into FP
Good point! This is all currently just a hobby. For Common Lisp specifically, the only things I've produced are a (mediocre) Battlesnake client and a (now defunct, as of yesterday) multiplayer word scramble game. Neither of these really derives much benefit from being created in Lisp, but I learned a lot along the way (which was really the point).
Unrelated to Common Lisp, I've found myself often needing to write code that generates code. This is an area where I suspect Lisp will shine, although I haven't had a chance to give it a try yet. Two examples from recent projects (which I tackled before ever thinking about using Common Lisp) are:
* Generating code to validate a particular JSON Schema (used in a static site generator)
* Generating JSX from Markdown (used for story content in a programming game)
To say nothing of the innumerable C macros I've written in my lifetime :)
Completely agree code generation is where I expect Lisp to perform the best. Though, I looked it up and apparently Markdown is not context-free so.. curious as to the challenges that introduces as I figure FP could zap through parsing a CFG but really struggle with state for something not context free
Yes, the article calls it powerful, but aside from the ability to update classes and their functions at runtime - which, maybe I’m missing the utility of so I won’t say it’s useless although in my experience a SharedInstanceSingleton or LocalImmutableConfig or BatchedMonitoringEvent wouldn’t need it - it’s “just” dynamically resolving the method implementation to use based on argument types dynamically and letting you provide an order of precedence for diamond inheritance.
I think it does solve one problem I have - with some solid tooling and a guarantee that class updates at runtime don’t disrupt ongoing calls, it might let me patch running binaries without doing a full update. Though, the cost is that I seem to incur a few extra lookups or even sorts on each dynamically dispatched method, to resolve the implementation? Besides that it doesn’t seem to really solve most problems I have regarding I/O or state - having the option to update some of these at runtime is interesting but it seems like something I’d only want to selectively enable
You also don't need to throw out all the good features of the other styles, as parts of functional programming are becoming more and more common in "regular" languages too. Rust uses functional patterns in many cases for instance.
And you can also write Lisp in an OO or imperative style if you want, it's no Haskell.
* Read some environment variables and a local file
* Start a monitoring thread that consumes from a channel or something similar, then every X s or X events writes to a local temp file and then sends a request batching some metrics to an external system
* Configure and start an http server
* Said server has a handler that 0. Starts a timer 1. loads, then increments an atomic “num requests served until now” variable 2. uses synchronization to lock on a list or ring buffer containing the last 5 requests’ user-agent headers 2.5 copies the current last 5 values, replaces oldest one with the one from the handles request, unlocks 3. generates a json response containing like “num_so_far: x, last5agent: [..], “some_env_var”:..” 3.5 stops the timer 4. write request user agent and time interval to monitoring thread’s channel 5. write response and end handling
* server’s gotta be able to do concurrency > 1 with parallelism
* On sigterm set the server to a state that rejects new requests, waits for existing requests to complete, then flushes the monitoring channel
I’d consider this a trial run of some of the most basic patterns commonly used by networked software: init io, immutable shared state, atomic mutable shared state, synchronization locked shared state, http ingress and egress, serialization, concurrency, parallelizarion, background threads, os signals, nontrivial cleanup. In Go, Java, or C++ I could write this with my eyes closed. How easy is it in Haskell or Lisp?
If you know of any demos or repos that do something like this - not a pure toy or barebones demo, but not a huge task all in all- in either I’d be interested in seeing what it looks like.
When I want "this will compile forever," I write it in ANSI C.
When I want "this will live forever," I write it Python 2.7 and make it the backbone of the entire org's infra templating. Bonus points if it's a custom Ansible module.