> 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. :)
Scheme is remarkably less interactive than Common Lisp, though specifics depend on dialect of Scheme.
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)
> People rave about their repl, but it feels like they're comparing it to a script iteration loop rather than static analysis tooling.
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.
> 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.
Many early Common Lisp systems had impressive error handling. Certain exceptions would automatically break to an interactive Lisp window.
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.
There was a book published just last year about the CL condition system for anyone who wants to better understand it (disclaimer: it's sitting on my desk but I haven't read it yet): https://link.springer.com/book/10.1007/978-1-4842-6134-7
It's like CSI vs Dr House. Debugging in python is forensic; you can figure out why your program died, but you can't really fix it. In common lisp, debugging can be curative as well as forensic. If you hit a problem like you call foo from bar, but foo isn't defined you can just write an implementation in your editor, send it to the running lisp and continue with the current execution. If a class is missing something, you can change the definition and not onl y have it take effect for all live instances, but you can also say how to transform the existing instances in your program. Have a look at the example given in the "docs": http://clhs.lisp.se/Body/f_upda_1.htm; you should get the gist even without knowing lisp.
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.
Yes, it was mentioned in the post: you normally get prompted not just when there's a breakpoint, but when your code calls something that doesn't yet exist, for example, or try to access a non-defined variable [1]... you have the option to "fix" the issue and continue from the same point, continue with a different value, or just let it crash... and there's stuff like profiling and de-compiling [2] which are all available directly from the REPL as well.
> You do know that statically typed languages have REPLs too? Like the ML family, including Haskell.
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.
> that REPLs are a poor substitute for many static analysis tasks.
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 ;).
> Maybe I should have asked you what you use the REPLs of statically typed languages for.
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.
I use repls a lot, both when I used clojure and now when I mainly use python. The reason for me is language exploration. I write my program and don’t always know how to do something and the internet is ambiguous.
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 should be obvious? He uses Racket, Racket has a very simple REPL, as opposed to CL which allows him to use the features he has heard mentioned about Lisp. Interactivity, short feedback loop, etc. This has been written thousands of times on here, I don't intend to repeat it here.
> I would spend a lot of time trying to figure out the actual type of the thing that I a given function
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.
Come on now, this is a silly take with more than a little elitism.
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.
I'm sorry, I know it can come out that way, but I am a developer that uses both statically typed languages and happily use powerful IDEs with them, and I also develop in a Lisp (Clojure more specifically), almost to a perfect 50/50 split.
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.
What would really help to persuade people that they'd be more productive in Lisp would be examples of software written in Lisp.
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.
If you've tried it properly and didn't personally found it compelling that's ok. I've seen people for who their mental model of how they think and approach computer tasks doesn't land to it, or people who just have other personal preferences.
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.
A typical standard lib may have thousands upon thousands of functions with many less commonly used arguments and options. If you are saying "memorise the lot" is the only route to productivity on lisp then I'm out, because that's unrealistic. And let's not get into libraries outside of the standard lib, am I expected to memorise XML parsers, graphics APIs, and so on as well?
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.
You don't have to. Lisp development environments remember all what one loads into them: the definitions, their source location, their arguments, their documentation, who calls whom, the data types, ... One can ask Lisp about all that then.
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.
I'm referring to what you commonly use, but also I'm talking about the part of the standard library that involves more basic operations like creating collections, iterating them, defining and requiring modules, creating objects, looping, destructuring, literals, conditionals, data types, polymorphism, error handling, etc.
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.
> my extensive experience with Python where things that are easy in, say, Go, are quite hard
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.
Yes, Python's REPL speeds up development, but Lisp's REPL (we should say image-based development model) is even more interactive! We never wait for a server to restart, our test data is live on the REPL, 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… it's crazy. From time to time it becomes a mess and I restart the Lisp image, but it's about every two weeks :p I tried to explain more here: https://lisp-journey.gitlab.io/pythonvslisp/
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.
Start your Python REPL, do your interactive development, measure your average time before aborting and reloading the python instance. How much time elapses? Minutes? Hours?
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.
indeed, I was thinking web development for that example, but I'm talking to my local development server, not a remote one (although it's trivially possible with a swank connection). I often have to wait for Django in a non-trivial app. I often set a ipython breakpoint, try things out, continue, and iterate.
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.
> 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?
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.)
The increased interactivity appears to be due to your particular framework, which you have built using Lisp but which is not the same as just "Lisp". Similar frameworks have, I believe, been built using Python.
no, forget the web server, there is more, I promise.
> 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.
Other than the "breakloop" functionality, which I agree is (AFAIK) unique to Lisp interpreters, I don't see anything described there that can't be done in a Python REPL.
If you can't understand what people are trying to say, I recommend trying to experience it for yourself. Install Common Lisp along with Slime for Emacs, and try things out. Or try out a Smalltalk environment like Squeak or Pharo which would also give you the same "enlightening" experience.
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!
> If you can't understand what people are trying to say
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.
> Lisp's REPL (we should say image-based development model)
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.
Code parsing in the Python REPL is different from that in Python source code files. Not MUCH different, but different.
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 understand the power of repl driven development that is difficult to explain outside the experience of it.
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.
That happens with languages in the Smalltalk family too, like Pharo. It's hard to convey the convenience of the Transcript window and the while environment without actually showing ir experiencing it.
But repl workflows are not special to Clojure and Scheme. People in JS set breakpoints in their IDE, pause at a point in their script to interactively explore program state all the time.
Not really. With Clojure you don't even have to leave your editor (I use Emacs with the fantastic CIDER package). You can put your cursor over an S-expression, hit two keys, and evaluate and see the results of the S-expression directly within the text buffer. The feedback loop is so quick it's basically instant - even tabbing over to a browser or terminal window feels lethargic in comparison.
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
In PhpStorm I hit a hotkey to set a break point, then a hotkey to make the API call to that code and immediately get an xdebug breakdown of the entire state of the program at that point. It’s almost instantaneous and I don’t have to switch windows. I would say that’s pretty close, although the upfront learning and config to get that working means it is higher overhead overall.
Part of it is cultural I think. In Clojure you'll often find yourself building your app incrementally, sending individual functions to the repl from your editor. I don't think there's any reason why you couldn't do that in Javascript.
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.
There is a nice little story in the book Practical Common Lisp (Peter Seibel, 2005). Quoting it below:
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.
Recently there was a bit of a fuss when people discovered the Mars Ingenuity helicopter runs python3. Now I wonder, if its software ever crashes, will people be able to just launch the python REPL and debug It?
After my Clojure conversion and baptism, I've taken to writing Python apps in a similar style using Jupyter notebooks, and find it to be a tolerable middle ground.
Especially if the Python is functional style with good referential transparency.
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..
I used to do this in Jupyter, but switched to vscode. If you have comments that look like:
# %%
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.
It is not quite the same. I'd argue that, while browser environments are very good, they're trying to solve a slightly different problem, and the overlap in capabilities is less than complete. Lispers sometimes say that the browser devtools experience is about halfway toward the power of a good Lisp environment, but I would argue that the converse is also true.
The greater Clojure hot-reload super power comes from embracing the idiomatic division of state and function though. Hot reload in C# will have probably have to re-instantiate classes or restart the app.
In clj, if your application state is kept in a global atom a'la
(defonce app-state (atom {}))
Then redefining your functions won't touch your data.
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 Common Lisp and Smalltalk take it a step further. You can go ahead and change the data structure in a way that's semantically incompatible. The runtime will notice that the definition has changed and either automatically reinitialize any existing instances to conform to the new definition, or, if it doesn't know how to do that, pose a UI asking you to tell it how.
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.
However, working with Clojure and Scheme, I understand the power of repl driven development that is difficult to explain outside the experience of it.