Why Lisp?
atlas.engineer
atlas.engineer
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 this vein, Standard ML (SML) is truly immortal, because the standard is set and it is "finished".
Just a fun thought!
https://www.vatican.va/roman_curia/institutions_connected/la...
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.
before that they were teaching it without any stress on grammar, mostly memorizing phrases and short sentences and learning how to use it
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.
Informal: used for emphasis or to express strong feeling while not being literally true. "I was literally blown away by the response I got."
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.
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.
Charles Dickens, "Nicholas Nickleby" -- "[h]is looks were haggard, and his limbs and body literally worn to the bone"
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. :)
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.
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.
Also, you did not mention Java. I have very old Java code that still works fine.
There's <<so much>> Java code churned out that it's unbelievable. Business systems have a ton more code than infrastructure systems.
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.
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.
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++.
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.
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.
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).
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 borrowedI have zero trust in Python as far as code longevity is concerned.
"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.
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.
I haven't seen py2 code in several years. Granted, I would look away in disgust if I did.
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.
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.
Browsers too take backwards compatibility very seriously and features stop working almost only if these are serious security holes.
However, the thing that broke Tuesday's app on Wednesday was probably a library or framework change.
:)
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.
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.
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.
-- https://www.bell-labs.com/usr/dmr/www/chist.html
Unfortunately since 1979, majority of C devs think they know better.
This single thing in isolation should never cast code as good or bad.
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.
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.
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.
Sure, Go could introduce breaking changes in 2.x. Will they? Track record says "no".
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).
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.
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.
I'm assuming here that "eternal language" doesn't necessarily mean "good 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.
Common LISP's tomb is indeed shiny and eternal. Developers at HN will mourn at its graveyard for eternity.
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?
But anyway, let's say I made an error somewhere and the program halts, putting me in the debugger. I can then examine each frame preceding the error. I can see every value going into my functions and jump around in the code generating those values. I change the code, while the debugger is still running, recompile it into the halted program and then pick a frame point right before the error occurred, to test if my changed code solves the problem. If it doesn't, I'll examine some more, perhaps start a second instance of the REPL to experiment a bit, then try another change in my code. All the while, I don't lose state. Once the problem is solved, my program will continue where it was as if the problem never happened.
For something like game programming, where you may be deep in the game and have encountered some rare combination of circumstances, this is simply invaluable.
I had a bug in a long running program that would only show up after like six hours. I couldn’t replicate it in isolation because it had something to do with how state was being maintained. I set a conditional breakpoint right before the crash, inspected the stack, patch the function, and then had it pick back up by reëvaluating the current function call.
Damn thing just worked. It was amazing, and saved me so much time.
> ...inspected the stack, patch the function, and then had it pick back up...
After you edit the live state of the program, how do you translate that into code sitting in source control? Do you save this blob of memory and pass that down through generations?
2. Modify the source code containing the function.
2a. Save the file.
3. Copy function definition to the debugger’s repl, and evaluate it.
4. Resume the system from frame that calls the function.
But yes, you must be diligent about making sure the source files contain the code that is actually running, but that’s not much different than artifact tracking.
Step a): One just evaluates from the sources, while changing them. Once done -> save sources.
Step b): Create a patch file, which changes the image. Such a patch file can be loaded while starting an image, until one decides to save a new image with the changes already loaded.
For example there is a new release for the commercial LispWorks IDE once every one or two years. Users usually get only patch files for bug fixes during the one or two years maintenance. Thus I have a directory for patches to LispWorks, which are loaded when I start the base image.
There are tutorials all over the place however as Smalltalk is a system that, like Lisp, has dialects, they tend to be system specific. Lots of material at http://stephane.ducasse.free.fr/FreeBooks.html .
Just because that feature is available and at one time was desirable (when machines were so slow, that compiling would break the flow), doesn't mean one needs to or should use it today.
https://devblogs.microsoft.com/java/hot-code-replacement-for...
As long as it doesn't change the class signature, you can happily edit-save-replace code, and it jumps to the top of the method you're editing. It's basically instant, so you don't suffer the javac and JVM startup cost.
I don't think I can do the same in more dynamic languages, like Ruby or Python. Or, maybe, my IDEs don't support it (IntelliJ suite).
If I was doing that every day, or even every week, I could see it being a huge advantage to incorporate a solution in the language—but at least for me it just doesn't appear to be an obstacle that often (incidentally my background was initially in game programming, too).
I think this is where the trip up is. In a game, for example, that state is often exceptionally complex and getting everything right back to where you came from is usually only possible running the full game and recreating the same state.
In most cases it’s not hard to narrow down which aspects of state are relevant; you really only need to preserve everything for rare exceedingly subtle/deep bugs, which is a special case not a general usage kinda thing—and yet this feature is often discussed as revolutionary for programming in general.
from ipdb import launch_ipdb_on_exception
with launch_ipdb_on_exception():
foo()Edit: Right now it is sponsored by EU grant! Maybe finally EU can do something useful
Still, I'm not gonna walk away from a perspective to get both usability and privacy together. What are your opinions on the matter?
How do people manage to get any real work done using languages that do not have large and thriving communities that can build and maintain various libraries?
For example, I mainly do applied ML work in bioinformatics. There really are no ecosystems that come close to those of Python and R in terms of their completeness for data science and machine learning libraries and infrastructure. I've looked at other libraries in other languages, hoping to switch, but since there are massive gaps, it's often a choice between building supporting libraries OR actually getting my work done.
I'm truly surprised anyone can build much of anything complex in languages with few libraries.
"Much of anything complex" probably wouldn't hold true for most developers...I mean, it's all complex given technology, but 99% glue code that can be written in anything. That's generally true for me at least.
I have been bitten by lack of libraries for things like Cassandra in the past and switched languages because of it, but that's the exception and not the norm for me, and you really have to choose the right language for some projects. Or have some kind of architecture where you can have different parts in different languages.
If my only way of debugging were to upload the compiled image onto an embedded target and observe the effects of print statements a serial log, I would still overwhelmingly want to be using Lisp.
"Forth takes a far different and far more pragmatic approach. The Forth designers do not assume what syntax, features or functions will be necessary in the future. The developers of Forth give you the full powers that they had to develop the language. You can develop macros for dynamically generating code. Without going into depth about how this mechanism works, a feat that was possible in Forth was the implementation of an object oriented programming system without having to change the compiler!"
It still seems to be a challenge to use programmable languages for day-to-day work despite how much fun they make programming for the programmer
Lisp sounds nicer.
However as the paragraph states, both begin at a level X but both do not stay at that level in the hands of one who understands how to use them. When used as designed, one does not program in these languages. You program in the language you create to solve the problem at hand.
An example at the extreme end of this idea is CoSY (Armstrong), an APL-like language written in Forth. A small example is a useable OOP extension written in ten lines. (Paysan)
My un-spoken point was that two independent language writers (McCarthy & Moore) found that similar ideas, expressed in that paragraph, implemented in radically different ways, improved their coding productivity.
It begs the question is their a lesson here for programming? It could be that IRL these concepts just don't work well or that they only suite a small subset of real world problems or a small subset of human brains, but it makes me wonder...
However, working with Clojure and Scheme, I understand the power of repl driven development that is difficult to explain outside the experience of it.
The only lisp I've dabbled with was Racket and I found the repl driven development to be a frustrating necessity. I would spend a lot of time trying to figure out the actual type of the thing that I a given function (even a stdlib function) needed, and futzing around in the repl seemed to be the fastest way to do it, but it was still quite slow.
It reminded me a lot of my extensive experience with Python where things that are easy in, say, Go, are quite hard. People rave about their repl, but it feels like they're comparing it to a script iteration loop rather than static analysis tooling.
That said, I completely buy the argument that Python's REPL is particularly bad and that there are other use cases where REPL-driven development shines. My mind is open, but these are the sorts of experiences the pro-REPL folk should be prepared to engage with in their evangelism. :)
Common Lisp is remarkably more geared towards interactive use, but while it does make it easy most of the time to be used with just minimal REPL - as in literal (loop (print (eval (read)))) - it's best to use integrated, enhanced environments like SLIME/SLY/SLIMV/etc or one of the IDEs (LispWorks, AllegroCL, Clozure CL IDE)
You do know that statically typed languages have REPLs too? Like the ML family, including Haskell.
And when using something like a Jupyter notebook with a kernel for your compiled language https://github.com/gopherdata/gophernotes you can do similar interactive programming.
REPLs are about trying out ideas or changes.
Lisp REPLs take that a step further, as you interact with and in your whole actually running program.
With python, I can add a signal callback which calls the inbuilt breakpoint() function. This means I can send my application a given signal, let's say SIGTERM, and it'll drop into they python debugger whereby I can then drop further into the python REPL.
Does Lisp provide more than that out of interest?
Edit: ah I see hot reloading is a thing. Not explored that in python.
But Common Lisp also has a fancy system for recovering from exceptions. So after you replaced the broken function, you'd often see a list of "restart" points from where you could retry a failed operation.
So even if you failed in the middle of a complex operation, you could test out a fix without needing to abandon the entire operation in progress.
I'm not entirely certain if this was as useful as it sounds, in practice, but it was certainly very impressive.
I'm overgeneralizing (I've had remote repls in production python application and used them to hotfix things), but only a little. You will appreciate the difference if you work with long running computations, where you don't want to have to start over from scratch if something went wrong right at the end.
[1] https://lispcookbook.github.io/cl-cookbook/debugging.html
[2] https://lispcookbook.github.io/cl-cookbook/performance.html
I do, but that I don't see how that relates to the bit of my post which you've quoted. I certainly didn't claim or imply that REPL and static type systems were mutually exclusive, only that REPLs are a poor substitute for many static analysis tasks.
> And when using something like a Jupyter notebook with a kernel for your compiled language https://github.com/gopherdata/gophernotes you can do similar interactive programming.
Yeah, I'm aware. I operate a large JupyterHub cluster (among many other things) at work. :)
> Lisp REPLs take that a step further, as you interact with and in your whole actually running program.
That sounds nice, but it's too abstract to persuade IMHO. Personally, it seems like REPLs can make certain feedback loops a bit faster, and to that extent they seem like a modest quality of life improvement; however, considering how lisp folks positively rave about them, I assume I'm missing something.
Maybe I should have asked you what you use the REPLs of statically typed languages for.
I actually never used REPLs for that (neither in Python, CL, Clojurescript nor Elixir).
> That sounds nice, but it's too abstract to persuade IMHO.
Of course it is. I also didn't get it until I've tried it myself. That actually has been the main cause why I tried (Common) Lisp at all (well I had been using Emacs Lisp before, but that doesn't really count ;).
I don't really use them, which is kind of my point. Once in a great while I'll do some quick testing in the rare case that using a REPL is actually faster than just running the program, but to even call that a marginal improvement in my quality of life would risk overemphasis.
With a nice repl I can rapidly iterate trying different things and doing micro in-situ experiments to explore and understand how the language (or library more frequently) works.
It’s very edifying.
You are indeed, Rackets REPL is very barebones. Use Common Lisp with either SLime or Sly and Emacs.
How puzzling that you do not spell-out exactly what you believe they are missing.
That's just because your were a noob. You hadn't learned and internalized the standard library yet, and you hadn't learned to understand flow and structure of types and functions and variables.
I too when I started with Lisp had the same struggles, but they go away with expertise.
That's why you'll hear people talk about "training wheels", when you program in a statically typed language with an extensive IDE doing a lot of static analysis for you, you basically operate constantly with training wheels on. You never really learn what class does what, what methods they have, what types things are, you rely heavily on your IDE.
For example, ask an experienced Java developer to write some Java in notepad? They don't know what to import, where to find anything, completely lost, productivity hits zero.
And I'm not degrading Java devs or that setup. What happens in Lisp is that you have to learn your language, you can't rely on an IDE or the compiler. In theory you could also learn Java to that level, stop using an IDE and you'll be forced to really know the language as well.
Here's the kicker? Once you learn the language to that level, your productivity in it skyrockets! But until you do, its a slow crawl.
And it's only ounce you've got that experience that the ability to change the program as it is running through a REPL really brings about speed, you don't use it to figure basic things like types and what standard functions exists, but to tackle real business logic or explore libraries and 3rd party APIs to get a grasp on those quickly.
Take this from someone who went from strongly typed Java, C++, C#, ActionScript 3 to a Lisp. I'm now way more productive in Lisp.
P.S.: Also, you'll have static analysis in Lisp as well, depending on which one and your setup. In Clojure, I have static analysis, an IDE, a connected REPL, and use all three actively.
An IDE (for a sufficiently static language) eliminates so much unnecessary mental load that is otherwise wasted on avoiding inevitable human errors—typos, type mismatches, forgotten cases.
I don’t want what is essentially trivia swimming around in my head any more than I want to find out about the inevitable errors I’ve made when I come to compile or run my program.
I want to know that no such issues exist well before then, so I can focus on making sure my program actually behaves correctly.
And I don't mean to say that you're a bad developer or not smart enough if you use an IDE. Of course you should use an IDE with Java or C# or C++. Those languages are all very good, and when paired with a good IDE can be quite productive. And even with a Lisp, you should and will want to use a powerful editor or IDE with good auto-complete, linting, jump to definitions, etc.
What I'm trying to say is that to be productive in a Lisp, it's a different learning curve.
You just can't come and say: "well I tried it a little bit, and I couldn't figure out how to do anything and I was really slow, so I just don't get the point of a REPL and I don't get why people claim it's a hugely productive tool."
I'm telling you why, it's like complaining that you type slower on a keyboard than you can write by hand for the first year of you using a keyboard. There's a learning curve.
You can't just say: "well I can't figure this out. Where is the IDE to help me with it? Where are the type definitions? Where is the compiler to guide me? I guess the Lispers are just full of it."
No, you didn't do what Lispers do. Lispers learn the language until those things are no longer a problem, and then they unlock a new found productivity combined with the ability to work inside a running program, quick iteration, being able to extend and mold the language to what is most efficient for you and your problem, etc.
It's 100% ok if you say that you don't feel it's necessary for you to go the extra mile to learn a Lisp properly. That's fine, if you find you're sufficiently successful and productive already with your language, that's totally cool. But you can't not properly learn it, and then claim that the claims of those who have are invalid.
It's like if you started to doubt a touch typist that's telling you that learning to touch type makes you a more productive typist, because when you tried it was hard to learn and you were slower at typing.
I was a teenage Lisp enthusiast and have seen these sorts of Lisp discussions go round and round in circles on online forums for the past couple of decades. Ultimately, the corpus of publicly available software written in Lisp just isn't that compelling.
It's often said (and may be true, for all I know) that there are lots of companies quietly using Lisp behind the scenes and reaping the benefits without shouting about it. But to be persuaded, people need to see results of the claimed productivity gains in the form of actually existing software.
It's also worth bearing in mind that there are lots of people (like me) who did give Lisp a serious try (Common Lisp in my case) and then moved on for various quite sensible reasons. Lisp enthusiasts sometimes speak as if anyone who doesn't love Lisp can't possibly have given it a thorough trying out, but I would question that assumption.
As for example of software made with a Lisp, I feel people are never happy with them, like a constantly moving goal post, but there's a certain chicken and egg to it.
Some examples:
- HackerNews
- Emacs
- LispMachines (the entire OS)
- AutoCad (uses Lips as a scripting language)
- Jak and Daxter (the first game)
- Yahoo Stores (pre-sale)
- Reddit (pre-IPO)
- Datomic (no-sql tuple DB)
- The flight system in the Boeing 747 Next (their latest 747 as of this writing)
- Apache Storm (the distributed stream processor)
- Walmart (they use it for backend stuff)
- CircleCI (build automation)
- Riemann (a high scale distributed event stream publisher)
- worldsingles network (bunch of dating websites)
- NuBank (a Brazilian bank)
- ConsumerReports (all their backend)
- The Climate Corp (agriculture software)
- Roam Research (note taking app)
- Kevel (ad server as a service)
- HighSpot (sales rep software)
- Reify Health (health related software)
Now those aren't all Common Lisp per-say, but Lisp languages so Arc, Common Lips, Scheme, Clojure, EmacsLisp, etc.There's also a lot of use of Lisp that's not like a closed software, a lot of times it'll be a backend service or parts of a system is using Lisp, I know Amazon, Microsoft and Netflix have some such use, it's not wide scale, but you'll find some teams there using some kind of Lisp, as you'll sometimes see job offers that mention it.
Something like C's K&R or Lua ones?? Its sooo handy to have a clear picture from the creators themselves.
If you are saying "memorise what you commonly use" then that's fine, except that any typical program usually involves a lot of "lesser used" stuff as well.
How? As in what commands/functions/macros?
Say I define a function and compile with slime, how do I get the repl to print it back to me? And with a function I loaded from an external library? Is there something like macroexpand-1 for functions which will print it out?
Common Lisp also has built in the function FUNCTION-LAMBDA-EXPRESSION, which returns the source for a function -> when Lisp has recorded its definition.
Try (DESCRIBE #'some-function) in SBCL to get information about the arglist, the types, the source file.
In a SLIME editor buffer try c-h m . This will display the buffer commands. There are a zillion of commands to get information about the symbols, code references, etc.
On my Lisp Machine:
Command: Show Source Code (a defined function spec) mts::init-facts
Source code for MTS::INIT-FACTS:
(defun init-facts ()
(setf *init-facts*
'((world (loc (actor joe) (val cave)))
(joe (loc (actor joe) (val cave)))
(world (loc (actor irving) (val oak-tree)))
(irving (loc (actor irving) (val oak-tree)))
(joe (loc (actor irving) (val oak-tree)))
(world (loc (actor water) (val river)))
(joe (loc (actor water) (val river)))
(world (loc (actor honey) (val elm-tree)))
(irving (loc (actor honey) (val elm-tree)))
(world (loc (actor worm) (val ground)))
(joe (loc (actor worm) (val ground)))
(irving (loc (actor joe) (val cave)))
(world (loc (actor fish) (val river)))
(irving (loc (actor fish) (val river))))))
Command: Show Callers (a symbol [default MTS::INIT-FACTS]) MTS::INIT-FACTS
MTS::MICRO-TALESPIN calls MTS::INIT-FACTS as a function.
MTS::MICRO-TALESPIN-DEMO calls MTS::INIT-FACTS as a function.
Done.
Command:As opposed to say learning the standard library relating to MIDI access, or file IO, or other specialized things that in theory could be a library and aren't really core.
It might sound a little like syntax, but because Lisps have such a lean syntax, there's a lot of the fundamental constructs that are part of the standard library and they are more what I'm referring too.
For example you'll have things like: car, cdr, append, mapcar, some, every, symbols, keywords, loop, intern, quote, format, butlast, intersection, union, multiple-value-bind, apply, funcall, =, eq, eql, if, unless, typecase, cond, dolist, iterate, etc.
What often happens in Lisps is that each core function is very powerful and becomes almost a mini-DSL. So it takes a while to really learn about all those and have them memorized by heart so you don't constantly need to refer to the documentation about what they do and how they work and how to use them. And often their power comes when they are combined together as well, and that also takes some time to learn all the valid ways of combining them.
Before you are familiar with all that and it's second nature, almost instinctive, you'll be a lot slower to read and write code.
Can you give some examples? This is not something I've encountered (although I have much more experience with Python than with Go so there is probably plenty in Go that I have not encountered).
> the argument that Python's REPL is particularly bad
What argument is this? This is also not something I've encountered. I use Python's REPL all the time and it greatly speeds up development for me.
Note that I'm not a 10x master, I use CL for mundane, real-world tasks (access to an FTP, parsing XML, DB access, showing products on a website, sending email with sendgrid…), my programs had 1 trivial bug, I am more productive and I have more fun… Just to say it has its uses, even today, even with "all the libs" of Python (and btw, see py4cl).
I'm not sure I understand. When I run Python's REPL, I'm running the interpreter on my local machine; I'm not talking to any server. The same would be true if I run the Lisp interpreter, but I don't see how that is any advantage for Lisp over Python.
When I've done Common Lisp heavily, I've lived in the same instance between multiple days up to weeks. This feels perfectly natural.
Admittedly it is hard to justify why this difference is meaningful.
Anyways, do you develop your app when you are in the REPL? Or do you try things out in the REPL and write back what you found out in the files? If you found how to send changes from your editor to the REPL, then good, but it's not built-in, and it will lack the other things I mentioned.
Yes. :-) Both strategies can be useful. (And note that "app" does not necessarily mean "web app"; web applications are just one kind of application. There are lots of different kinds of applications. The REPL helps with all of them.)
> we compile our code function-by-function with a keystroke and we get instant warnings and errors, when there is an error we get an interactive debugger that pops up and allows us to inspect the stack, go to the buggy line, recompile, come back to the REPL, resume the execution from the point we want, objects are updated when a class definition changes and we can even control that.
I know you are skeptical because python has a repl too and so it is hard to see what these "live" environments like lisp/smalltalk are offering. But trust me (and the others) that there is more than what python offers.
I have written python professionally probably more than any other language I have used, so I'm not ignorant of python btw.
No one is trying to fool you. But you don't have to take our word, please try it out for yourself!
I understand it just fine. I've used a Lisp REPL. I'm just pointing out that not all of the features of the Lisp REPL that are being mentioned are unique to Lisp.
> I know you are skeptical
I'm not "skeptical" at all about what can be done in a Lisp REPL. As I just said, I've used it. I have not claimed that a single thing anyone has said about the Lisp REPL is false.
I think you are reading things into my posts that I have not said.
> you don't have to take our word, please try it out for yourself
As I said above, I already have. Don't jump to conclusions.
This looks like something specific to your particular development workflow for one particular type of application (a web server). REPLs (for both Lisp and Python) are much, much, much more general than that.
Specifically, if you try to paste a function definition that has blank lines in it, into the REPL, the first blank line will be interpreted as "end of function" and the rest of the function will become incorrectly indented garbage.
I've been having trouble with this as well. I think we'd do best in removing "REPL" from any explainations, and instead use the words "Interactive Development", "Dynamic Development", "Hot Reload on Steroids" or something similar, as when I use terms like that, people understand it's nothing like the "repls" ("shells" really) other languages provides.
The first Lisp, called LISP I and its successors, from John McCarthy and his team had a remarkable collection of innovative technology:
* a reader for symbolic expressions. These are nested lists of symbols and other data objects like numbers and strings
* a printer for symbolic expressions.
* an evaluator for symbolic expressions. Each symbolic expression evaluates to a value. Side effects to the running Lisp could be added variables, data or function definitions.
* an automatic garbage collector taking care of freeing no longer used memory
* piece-by-piece allocation of data
* a form of programming with functions, also using recursion
* a way for Lisp to store and restore the main memory heap to/from external storage
* Lisp compiler written in Lisp, which generates assembler, and an assembler which creates code into the runtime -> the first interactive incremental to memory compiler
* a way to program with Lisp code generated by Lisp macros
It had a batch REPL. This Read-Eval-Print-Loop would read a file of expressions, expression by expression, evaluate them and print each return value into a file. It used the above functions READ, EVAL and PRINT.
Lisp was starting up and restoring a default image as the heap. It would then execute the file via the batch REPL and save a new, changed image as the new default image. Next time Lisp starts, it would then restore the latest image.
A next step in the evolution was then to connect a terminal and let the user enter a Lisp expression. Lisp reads it, evaluates it and prints the value back. Side effects again might change the running Lisp. The REPL then waits for the next terminal input. Code could also be loaded from files.
That way the REPL was not some interface which the debugger brings up on request. It was the main interface for the programmer to build up large Lisp systems piece by piece.
We are talking about the early 60s here.
In the coming decades the idea of a REPL morphed into extensive interactive resident development environments like Interlisp <http://interlisp.org>. Interlisp combined the REPL with managed source code, structure editing for code and data, automatic error correction of code, undo of evaluation effects, a virtual machine, ... and a lot more. Here Lisp, the IDE and the programs under development were running in one Lisp runtime. We are talking about the early 70s.
Interlisp moved at Xerox PARC onto their new graphical workstations. Thus the REPL-based development environment moved to graphical user interfaces. The Interlisp team got an ACM Award for their pioneering work.
The MIT AI Lab took that idea to their graphical Lisp Machines.
Today in 2021 Lisp programmers often use SBCL as the implementation, GNU Emacs and SLIME (or similar) as their development environment. It's still usual to develop a running program, having potential multiple REPLs into the program, using Emacs/SLIME to send code for remote evaluation to the running Lisp.
GNU Emacs itself is a Lisp program, too - with its own integrated Lisp and the development tools for it. GNU Emacs has its own REPL, called IELM, the interaction mode for Emacs Lisp.
Even in Lisp not all development interactions goes through a REPL, but there are still a few very powerful ones, like those of Allegro CL, LispWorks, McCLIM, Interlisp (recently open sourced) or the Lisp Listener from Symbolics Genera. Typical is the rich interactive error handling in multilevel break loops with restarts and the ability to continue from errors. The above also support image-based development and integrate Lisp, IDE and the software under development in one program.
Is this not the same?
I haven't used Common Lisp much, but I've seen a few really impressive demos of debugging in its available REPLs. This is a longer one, but a good tour of SLIME (also an Emacs package): https://youtu.be/_B_4vhsmRRI
Here is a video I recorded which hopefully demonstrates just how much faster you can go in Clojure
Like I think a parent comment said it's hard to explain but once you get it, it's fun malable and exploratory
Intellji also has a debugger that works with Clojure for when it's appropriate
In php you can't redefine a function at runtime without something like runkit, in Clojure you can fix your systems at runtime
Common Lisp takes this to another level though with stuff like restarts. Where the app will throw an exception during development (perhaps because a function isn't implemented yet) and instead of crashing you'll end up in the repl where you can make some fixes and keep running the app.
An even more impressive instance of remote debugging occurred on NASA's 1998 Deep Space 1 mission. A half year after the space craft launched, a bit of Lisp code was going to control the spacecraft for two days while conducting a sequence of experiments. Unfortunately, a subtle race condition in the code had escaped detection during ground testing and was already in space. When the bug manifested in the wild--100 million miles away from Earth--the team was able to diagnose and fix the running code, allowing the experiments to complete. One of the programmers described it as follows:
Debugging a program running on a $100M piece of hardware that is 100 million miles away is an interesting experience. Having a read-eval-print loop running on the spacecraft proved invaluable in finding and fixing the problem.
That looks like about 9 light minutes away, so about 18 minute latency?
It might be super epic, but doesn't sound super nice :)
I.e. in each notebook cell, prototype and exercise your function in isolation. Then copy them over the real py module.
Still, I miss the elegant data literals in Clojure whenever I'm slogging through some literal Dataclass objects for testing..
# %%
It will treat the following chunk of code like like a Jupyter cell and provide inline ui to run it. It’s almost the same thing as a notebook, but no markdown and your file is just a plain python script.The only reason to do a full restart would be if you changed the data structure expected by the functions in a way that's not semantically backwards compatible.
Big-whoop on a stateless webserver, maybe, but on the front end using ClojureScript for an SPA it is nothing short of magical and has ruined any other browser dev work flow for me.
And when control reaches a function that expects the old kind of object and runs into an error, it'll enter a breakloop that you can use to modify the now-out-of-date function to handle the new definition properly. Once you've redefined the function you can then tell the runtime to resume and it will proceed as if the function you called had originally had the new definition.
https://mikelevins.github.io/posts/2020-12-18-repl-driven/
https://mikelevins.github.io/posts/2020-02-03-programming-as...
not even close, no
There is also very particular textual representation of Lisp.
But there could be others.
The general workflow is: compile and load your program and tests, run the tests, change the code and tests, recompile and reload changed files (using an internal dependency manager that is something like a make system), and continue.
The focus on the repl is a bit misleading, since all the changes are off in files. I mean, you CAN type in a new function definition to the repl, but there's not much point.
R code from the 90s still runs today on the latest versions and everyone uses a REPL to write their code. I don't see a lot of people jumping into R any time soon, quite the opposite in fact.
The internals itself in R actually sound quite lispy: both values and codes are SEXP (S-expressions).
R is inspired by Scheme and Common Lisp; it has first-class functions, multiple dispatch.
https://www.stat.auckland.ac.nz/~ihaka/downloads/Interface98...
"This decision, more than anything else, has driven the direction that R development has taken. As noted above, there are quite strong similarities between Scheme and S, and the adoption of the S syntax for our interpreter produced something which “felt” remarkably close to S."
I parse code, every time I read source. Ease-of-parsing is absolutely a quality worth optimizing for. (The difference is, ease for who? Optimize for a human parser, not a machine parser.)
So yeah, I can glance at a Common Lisp program and tell pretty quickly what it's doing. That comes from studying 10,000 Common Lisp programs. It's pattern recognition.
I have had the same experience with C++ that I wrote years ago.