Immutability. (…) This means you’ll tend to program with values, not side-effects. As such, programming languages which make it practical to program with immutable data structures are more REPL-friendly.
Top-level definitions. Working at the REPL consists of (re-)defining data and behaviour globally.
These two points are in furious contradiction, since redefining top-level definitions is pretty much the ultimate side effect. Every other top-level function can see this “action at a distance”.
Let me be perfectly clear: ML and Haskell allow you to program using Lisp’s “every definition is subject to revision” style. Just stuff all your top-level definitions into mutable cells. The reason why it’s not done as frequently as in Lisp-land is because, perhaps, perhaps, this is actually a bad idea.
Funny, I’d consider the alternative, restarting a process, to be “the ultimate side effect”.
How so? There’s no local reasoning about the old process being defeated, precisely because you have discarded the old process.
Wrapping every definition with performUnsafeIO/readMVar would be prohibitive ceremony.
It pales in comparison to the ceremony of re-proving a large chunk of your program correct, because a local syntactic change had global consequences on the program’s meaning.
Why? My experience is that it’s a really great idea.
Because… how do you guarantee anything useful about something that doesn’t have a stable meaning?
There’s no local reasoning about the old process being defeated
Does your program exist in isolation? Useful programs needs to deal with stateful services, such as a database. Why shouldn’t your compiler/language/runtime offer tools for dealing with the same set of problems?
It pales in comparison to the ceremony of re-proving a large chunk of your program correct
I don’t understand this point at all. If your “proof” is a type checker, then just run the type checker on the new code…
how do you guarantee anything useful about something that doesn’t have a stable meaning?
You don’t. You stop changing the meaning when you want to make guarantees about it. You don’t need all guarantees at all times, especially during development.
Again, consider stateful services. Do you run tests against the production database? Or do you use a fresh/empty database? If you need something to stand still, you can hold it still.
Useful programs needs to deal with stateful services, such as a database.
When they’re running, not when I’m writing them.
Why shouldn’t your compiler/language/runtime offer tools for dealing with the same set of problems?
It isn’t immediately clear to me what kind of problems you’re thinking of, that are best solved by arbitrarily tinkering with the state of a running process.
If your “proof” is a type checker, then just run the type checker on the new code…
It is not. Some things I prefer to prove by hand, since it takes less time.
You don’t need all guarantees at all times, especially during development.
It’s during development that I need those guarantees the most, since, after a program has been deployed, it’s too late to fix anything.
Your production system is always running. Is it not?
what kind of problems you’re thinking of, that are best solved by arbitrarily tinkering with the state of a running process
Who says it is “arbitrary”? I very thoughtfully decide when to change the state of my running processes. When you change one service out of 100 in a production environment, is that not “rebinding” a definition? Why should programming in the small be so different than programming in the large?
Have you ever worked on a UI with hot-loading? It’s so nice to re-render the view without having to re-navigate to where I was. Or to write custom code to snapshot and recover my state between program runs.
What about a game? What if I want to tweak a monster’s AI routine without having to find through a dozen other monsters to get to the exact situation I want to test? I should be able to change the monster’s behavior at runtime. Great idea to me.
Proof is useful, but it’s not everything, and it’s not even clear that it’s meaningfully harmed by having added dynamism. Instead of proving X, you can prove X if static(Y).
This thread is a direct illustration of the incommensurability between the systems paradigm and the programming language paradigm, as described in Richard Gabriel’s “The Structure of a Programming Language Revolution” https://www.dreamsongs.com/Files/Incommensurability.pdf
The Facebook “Process” is a great example of something that never needs restarting but rather features and services are changed or added to the application while it is running in front of our eyes - no page refresh required in the Web version at least.
I’d say that from a customer’s perspective this is a really good thing, and continuous end-user improvement is the long tail of continuous integration where nobody need do a reinstall ever again.
I realize that this is largely philosophical at this point, but we are starting to have the tools to make this possible in a more general setting.
For me as a user it’s a good thing I think, since I don’t need to lose context in huge point releases. Oh look, edit mesh just appeared on my toolbar. I wonder what that does?
Immutability here refers to data and how the state is managed in the application. Since Clojure uses immutable data structures, majority of functions are pure and don’t rely on outside state. This makes it easy to reload any functions without worrying about your app getting in a bad state.
These two points are in furious contradiction, since redefining top-level definitions is pretty much the ultimate side effect. Every other top-level function can see this “action at a distance”.
Let me be perfectly clear: ML and Haskell allow you to program using Lisp’s “every definition is subject to revision” style. Just stuff all your top-level definitions into mutable cells. The reason why it’s not done as frequently as in Lisp-land is because, perhaps, perhaps, this is actually a bad idea.
Funny, I’d consider the alternative, restarting a process, to be “the ultimate side effect”.
Because defaults matter. Wrapping every definition with performUnsafeIO/readMVar would be prohibitive ceremony.
Why? My experience is that it’s a really great idea.
How so? There’s no local reasoning about the old process being defeated, precisely because you have discarded the old process.
It pales in comparison to the ceremony of re-proving a large chunk of your program correct, because a local syntactic change had global consequences on the program’s meaning.
Because… how do you guarantee anything useful about something that doesn’t have a stable meaning?
Does your program exist in isolation? Useful programs needs to deal with stateful services, such as a database. Why shouldn’t your compiler/language/runtime offer tools for dealing with the same set of problems?
I don’t understand this point at all. If your “proof” is a type checker, then just run the type checker on the new code…
You don’t. You stop changing the meaning when you want to make guarantees about it. You don’t need all guarantees at all times, especially during development.
Again, consider stateful services. Do you run tests against the production database? Or do you use a fresh/empty database? If you need something to stand still, you can hold it still.
When they’re running, not when I’m writing them.
It isn’t immediately clear to me what kind of problems you’re thinking of, that are best solved by arbitrarily tinkering with the state of a running process.
It is not. Some things I prefer to prove by hand, since it takes less time.
It’s during development that I need those guarantees the most, since, after a program has been deployed, it’s too late to fix anything.
Your production system is always running. Is it not?
Who says it is “arbitrary”? I very thoughtfully decide when to change the state of my running processes. When you change one service out of 100 in a production environment, is that not “rebinding” a definition? Why should programming in the small be so different than programming in the large?
Have you ever worked on a UI with hot-loading? It’s so nice to re-render the view without having to re-navigate to where I was. Or to write custom code to snapshot and recover my state between program runs.
What about a game? What if I want to tweak a monster’s AI routine without having to find through a dozen other monsters to get to the exact situation I want to test? I should be able to change the monster’s behavior at runtime. Great idea to me.
Proof is useful, but it’s not everything, and it’s not even clear that it’s meaningfully harmed by having added dynamism. Instead of proving
X, you can proveX if static(Y).This thread is a direct illustration of the incommensurability between the systems paradigm and the programming language paradigm, as described in Richard Gabriel’s “The Structure of a Programming Language Revolution” https://www.dreamsongs.com/Files/Incommensurability.pdf
What a wonderful piece of writing. Thanks a lot for sharing this.
I’d just like to chip in here with one word
“Facebook”
The Facebook “Process” is a great example of something that never needs restarting but rather features and services are changed or added to the application while it is running in front of our eyes - no page refresh required in the Web version at least.
I’d say that from a customer’s perspective this is a really good thing, and continuous end-user improvement is the long tail of continuous integration where nobody need do a reinstall ever again.
I realize that this is largely philosophical at this point, but we are starting to have the tools to make this possible in a more general setting.
For me as a user it’s a good thing I think, since I don’t need to lose context in huge point releases. Oh look, edit mesh just appeared on my toolbar. I wonder what that does?
So I guess I’m with Brandon on this one.
Immutability here refers to data and how the state is managed in the application. Since Clojure uses immutable data structures, majority of functions are pure and don’t rely on outside state. This makes it easy to reload any functions without worrying about your app getting in a bad state.