If you want the least amount of friction possible, install Racket and run DrRacket. Here's a tutorial: https://docs.racket-lang.org/quick/index.html and one to make web apps: https://docs.racket-lang.org/more/index.html
(I don't mean to disparage Clojure, I'm sure it's great, but a newbie needs something easy to get excited about, not having to mess with JDK and setting up Emacs)
Go with Lisp instead. Or OCaml. Both are purer and leaner than the JVM-addled morass that is Clojure.
When I am programming in Lisp, I enter a sort of zen like state where my mind becomes one with the problem. Now don't get me wrong. I can write a lot of Go, Python, Rust, what have you on some days when I am working on a good problem and there are no meetings to distract me. On some days, I can do this for 6-10 hours straight with little breaks. So I don't think I have the ADHD problem that the parent or the OP is talking about.
But I still have this nagging feeling that the language tooling, the ecosystem, the breaking dependencies, the "making the compiler happy", the stupid language restrictions and such distract me from my problem domain.
But not so with Lisp. With Lisp, it's just me, the problem, the algorithms to solve it and the code. Nothing else. It is a feeling of hyperfocus (a word you so rightly chose). For some reason I cannot get into that state with other languages and I could never understand why.
Your comment is on to something here and this might very well explain he zen-like hyperfocus feeling of immersing fully into the problem domain I get with Lisp.
Sometimes it's nice to shed all that bullshit and just create something.
Sometimes, only on languages with the least amount of syntax and semantics you're able to sculpt that fleeting idea you had. Inspiration is very delicate, and it doesn't take a lot for it to die down because you forgot to update npm and your Emacs config is borked.
> But I still have this nagging feeling with other mainstream languages that their tooling, their ecosystem, the breaking dependencies, the "making the compiler happy", the stupid language restrictions and such things distract me from my problem domain.
I can relate to this in a different way.It seems the vast majority of work today is not "the problem", per se, it's the interface that exposes the problem (or solution) to others. Shoving stuff in and out of format (JSON, SQL, etc.), marshaling the data over interfaces, or building UIs to expose and work with it, etc. The problem domains themselves, for a lot of systems, can be pretty simple.
But with CL, with the reader, and things like Emacs, a lot of that falls into the background -- with the caveat "as long as you're willing to work with native representations", i.e. S-expressions.
I have an "application" in CL, and it's 1500 lines of code. Outside of a couple prints for debug, there is no I/O. No user interface. Nothing. It's all "just code". It's all "just problem".
Meanwhile, I have a Java program I wrote. It wraps a 1000 line Java utility into a GUI. There's 4000 lines of code to present that GUI. It's a not quite fair comparison, as the entire purpose of the application is to expose this utility into a "usable form".
But, with Java and other languages, there's always going to be some level of that. With CL, again if it fits within the confines of the REPL and S-expressions, you get all of that "for free".
Trivial case with a simple Java command is you need some way to parse the command line options. Extended lambda lists give you that "for free" in CL. Named parameters, optional parameters, default values, etc. Arguably the entire point of the extended lambda list descends from their use within environments like Lisp Machines, where the function arguments WERE the "command line", and that's why its so robust. Those are all nice features when writing code, but they're really nice when you have to type stuff in all the time.
And this ability to not worry so much about external representation, and moving and converting data, instead just focusing on what's being done to the data, in native terms, it's very liberating.
There is no "impedance mismatch". Just code, just data, and code is data.
Either way this comment makes me want to try a LISP! I might give Clojure a go, I daily drive another alt-JVM language at the moment so it’s a natural choice.
As for treatments there basically isn’t one. Among other things I experimentally microdosed quetiapine against it (under the theory dopamine might be involved) under the supervision of a doctor which basically cured the visual distortion but not the pain, but I quickly built a tolerance and binned it off because the side effects aren’t great. Some people benefit from anti-seizure medications but not everyone and the side effects can also be bleak. Fortunately medical cannabis is legal in the UK and it’s very effective against the pain, the amount of unsuccessful things I’ve tried qualifies me for it and the pain’s only a problem on very bad days now. Coding with the monofur font helps me too!
Of course everyone has some noise in their visual system, a little bit of TV static isn’t necessarily indicative of disease. For reference I struggle to read when it’s bad, and my night vision is impaired quite a bit compared to what it was before so you’d definitely notice if it was problematic.
One anecdotal thing from support groups etc that makes me cautious is that while some people are born with it and some people contract it spontaneously later in life (this was my case), other people can contract it after taking various types of drug. Typically these are serotonergic drugs like antidepressants, psychedelics, and that sort of thing which aren't in the same category so it'd probably be okay but it's definitely possible I can make things worse as well as better.
Maybe there are business reasons to avoid less popular programming languages, but there aren't any programming reasons to avoid them. Maybe we should stop treating tools as if they were candidates for homecoming king and queen.
While most languages are akin to preparing a detailed blueprint and executing it (then stop to update the blueprint and try again), exploratory programming is like sculpture. You have an unformed mass full of unrealised potential, and you slowly carve it to shape.
The ADHD/Bipolar mind of the article isn't great at making plans, it's great at following its gut instinct, making shit up step by step.
Lisp enables this flow, but also Forth, Smalltalk, Self, etc., which I've been deeply obsessed with lately. Sadly, all we have today on the mainstream are basic REPLs which are a harmless, defanged version of these old environments promoting creativity and flow state.
Which is why I do best with the command line, piping outputs and rapidly iterating to get the output I want.
And why I struggled so much writing Python in a larger, more complex script/app. I wanted to constantly run and rerun the smaller parts to get each one just right. Unfortunately that doesn't work great when you're interacting with a half dozen different APIs that take minutes to run each time. Until I realized what was slowing me down, I was just getting more and frustrated.
Now I am at least aware of the issue. Part and parcel of growing from sysadmin to DevOps to coder, I guess? Now I know to look for shims or stubs that help me. Now I can write tests that isolate and verify, rather than run the whole damn thing end to end.
It helps me to think of objects as those command line tools. Their method parameters are the stdin that get passed through pipes. I apologize that this isn't particularly profound, but sometimes epiphanies are made from smaller realizations.
Maybe I should have tried writing part of my most recent script in Lisp? Just to see if it resonated like described in this thread?
> Maybe I should have tried writing part of my most recent script in Lisp?
90% of Lisp programming is selecting an expression in your editor and pressing C-x C-e to execute it. And continue until you are satisfied it operates as you want, then integrate it in the larger system.
One one hand, as you alluded too, I've found unit tests are a great solution to this. Kill two birds with one stone - rapidly iterate on a chunk of code and come out of it with solid test coverage.
It's too bad that's not very feasible in gamedev most of the time with so much going on in the world, but over time I think that environment improved my skills to where it's less of a problem.
Can you elaborate on that? I think you are saying that REPLs in other languages don't do what you can do in Lisp, but I don't think I know enough to fully understand why.
You start a fresh REPL session with a vague idea of something you want to build. You have good tooling that enables you to write code in your favourite editor and pipe it to the REPL through a socket. You write a couple of functions and send them to the REPL. You call them a few times with some test arguments, maybe define a couple global variables while testing, and realize one of the functions doesn't quite behave the way you want. You tweak the code and send it again to the REPL. The function is redefined and works properly now.
You're happy with what you have for now, so you save the entire current environment of the REPL, functions and global variables and all, into a file (called an image) on your disk. The next day, you come up with a great new feature so you load up the image and continue exactly where you left off yesterday, with all your functions and even global variables as they were defined previously. You add your new feature, all while testing and tweaking your functions interactively, and then you save the new environment into an updated image. Any other code that loads that image will be able to take advantage of your updated functions and variables now.
The power of lisp is not just the fact that there's a REPL — a lot of languages have that now. It's the ability to save and reload the entire current state of the REPL (interactively or programmatically), which enables the powerful interactive development that so many lispers, including myself, rave about.
In the end what you have is a base image, and then a set of code and instructions to transform that image into your runtime image, then you distribute that image as your "application".
I have never needed to do this, as all of my work has been utilitarian in nature, rather than some kind of monolith distributed to others. I simply load my code on to the stock image and go from there.
But even then, for folks that do that, they may well load up the stock image with useful and common, personal, utilities, and snapshot that. Their "application" is their essential working environment.
Smalltalk does, and Forth can work from this concept of an image.
Schemes tend to not be this way. They are interactive, but the image concept is not as ingrained in the Scheme world. Instead, it's more like doing development in something like Bash -- an interactive environment to be sure, but one that always resets to the baseline state and must be reloaded when started. There are certainly Schemes that can do this, it's just not as routine.
It particularly resonates with me because I think so many project teams neglect the Developer Experience. In my opinion DevOps should be more about making coding, testing and deploying as frictionless as possible.
now it just seems like a hundred children you have to pacify and get to bed before you can even start working
I was motivated to do this because I had a ton of linguistic and text data in my environment which back then took a little while to load. Now, I organize everything I do into small or sometimes tiny Quicklisp projects/libraries and that is much more convenient for me.
If you want to really get into image based programming, the open source Pharo Smalltalk is very good.
Because there's a problem I've run into several times when writing Lisp (I use SLIME): I decide to refactor some stuff, so I rewrite some stuff, maybe remove a function or two from the source as part of that. Things seem to be working fine. I save my .lisp files and then at some point end up restarting the Lisp process (often because McCLIM can be kind of flaky and lose its connection to the X server). When I go to load my code back in, I get errors, because oops I was actually still calling that deleted function somewhere -- it "worked" before because the old function was still hanging out in the image.
When I develop in a compiled language, I just accept that I'm going to be re-building the entire thing from scratch (equivalent to restarting the lisp process & re-loading the source files) every time I want to try a change; if I broke something, I'll find out the next time I run make/go build/whatever. When I'm developing in Lisp, and I'll freely accept that my Lisp dev flow probably isn't ideal, it's so easy to screw up but be unaware of it for hours if not days: I write some code, I'm happy, I close Emacs, then a week later I come back and try to load my package and it's just broken.
I could solve it for myself by just using images: the "deleted" function would persist in the image, and every time I started working again it would be there. But if I ever want to let somebody look at the code, hack on it, make changes, what's that look like?
"batch compiled from scratch"
OTOH, most usage of Common Lisp is compiled, often by an incremental in-image compiler
> because oops I was actually still calling that deleted function somewhere
Typically Lisp IDEs have features like Edit Callers and/or List Callers. I would
1) "Edit Callers" -> this walks me through all functions which use this function, I change the functions and I can edit/compile them
2) When I'm done, I use "Undefine Function", which then undefines the function from the running Lisp.
That's the key; if you delete a function make sure to undefine it so it's not sticking around in the image.
In slime you can use `C-c C-u` which is bound to FMAKUNBOUND.
this used to be the normal way of programming in lisp repls (like in 01980) but maybe this is good enough
* (defun tri (n) (loop for i from 1 to n summing n))
TRI
* (describe 'tri)
COMMON-LISP-USER::TRI
[symbol]
TRI names a compiled function:
Lambda-list: (N)
Derived type: (FUNCTION (T) (VALUES NUMBER &OPTIONAL))
Source form:
(LAMBDA (N)
(BLOCK TRI
(LOOP FOR I FROM 1 TO N
SUMMING N)))
> When I go to load my code back in, I get errors, because oops I was actually still calling that deleted function somewhere -- it "worked" before because the old function was still hanging out in the image.so a thing you can do in that case is to delete it from the image at the same time
well, you can't really delete it from the image, but you can remove the function binding of the symbol, and since normally in common lisp implementations function calls indirect through the symbol (which is why you can replace the definition with a newer one) deleting the function binding is enough
* (defun tri0 (n) (tri (1+ n)))
TRI0
* (tri0 4)
25
* (tri0 5)
36
* (fmakunbound 'tri)
TRI
* (tri0 5)
debugger invoked on a UNDEFINED-FUNCTION @52A00674 in thread
#<THREAD "main thread" RUNNING {1001348003}>:
The function COMMON-LISP-USER::TRI is undefined.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE ] Retry calling TRI.
1: [USE-VALUE ] Call specified function.
2: [RETURN-VALUE ] Return specified values.
3: [RETURN-NOTHING] Return zero values.
4: [ABORT ] Exit debugger, returning to top level.
("undefined function" 6)
0]
as rainer pointed out, slime has a command for this (i barely use cl myself, you know much more than i do)I also use the REPL for quick tests, and evaluate new code by writing it in emacs and compiling it one function at a time (C-c C-c).
Finally, I keep files of tutorial/demo/test cases which work a bit like a notebook, allowing me to quickly verify that existing 3D scenes still work.
admittedly it's hard to save the state of the page after you hack on it in the browser console, but jupyter doesn't really have that problem
Same. That has tripped me so many times when trying out something simple in golang.
I get why they are undesirable in the long run, but in the short run I'm often trying out a fragment and don't have the users of every variable written yet.
When you're exploring, you don't want your compiler to stop you in your tracks to warn you about pedantic nonsense. It's not a syntax error that prevents your compiler from understanding, it's just arbitrary restriction that the compiler decides to pester your about, a sort of a robotic grammar nazi.
Computers are obtuse, the worst thing they can do is ignore your perfectly parseable and valid instructions for arbitrary reasons. Machine, if you know I'm not using a variable, kindly let me know and carry on doing your job.
i don't actually know what he was hacking on at berkeley
You guys want to have your cake and eat it too -- to be correct despite being lazy and sloppy.
It makes no sense. Bite the bullet. Don't have unused variables. There's no such thing as "exploration". After you've "explored" enough (where "explored" means "coded sloppily"), you've quickly exhausted your mental buffer, and it's suddenly expensive to go back and polish what you've done, making yesterday's exploration today's (hopefully not too creaky) foundation.
This is one where faster local development (because you don't have to repeatedly, jarringly stop what you're doing and patch up something that doesn't matter) and clean source in a common repo are both possible and very easy to achieve.
But whatever. Yay go.
If I need to use analysers to fix that, I already have those for C and C++ for the last 30 years.
I think while some of the people he describes may have ADHD, very few people with ADHD are this person. His description resonated with me as a particular archetype, one that often makes their way into grad school eventually. Often enough to be recognizable.
I also don't think the author meant these people are bipolar in a medical sense, but used "bipolar" as the success+failure designation, perhaps unfortunately.
If you're interested in trying to make this work, we can try implementing it first in Python using a shim annotation on every function and making some changes to an existing debugger plugin, it will definitely be difficult to integrate but it should be doable.
For C++, something similar can be done by hacking the compiler to make all functions virtual and then hooking up NativeJIT to recompile functions as-needed. Eventually using something like rr we can even replay execution after we change data structures.
I really resonate with the idea of exploratory programming and find myself in a similar position to you - but I can't use Lisp for most of my work, so given the possibility, I really do think that making tooling that makes this kind of development environment possible is the best option, and I'm pretty sure it's possible to do it, so why not have the best of both worlds?
I also don't see any technical reason you couldn't have the best of both worlds. E.g. a dynamic runtime environment that allows recompiling functions on the fly etc., but also have a separate batch compiler that can type check the program as a whole, do whole program optimizations, etc.
So you can do exploratory programming while developing, and the run the batch compiler before committing changes, when doing releases, etc.
I think the biggest challenge is simply the amount of effort required to design and implement it (and of course someone who's interested enough in it to do it).
Focus is a precious, rare resource outside my control. Mainstream languages, made for collaboration in big teams rather than maximum personal freedom, soon impose restrictions that kill hyperfocus. The "unused variable is an error" is the most egregious of them that I hate with a passion. The separation between compilation and running is another that seemingly inescapable today. With languages like Lisp, I can code at the speed of thought from prototype to production.
These days tech is advanced by FAANGs, so no one cares about removing the safeties from our languages; this is the reason the focus-challenged among us still go back to Lisp and other lost languages (Forth, Smalltalk, Self) that have disappeared from the mainstream.
Rust, C++, Go, Python, etc. are good, but they're not made for us.