11,571 karma · joined November 23, 2009
PGP key (for email alias): https://www.thejach.com/about#pgp
Available for IM on Matrix/Element -- Jach#matrix.org. Discord as jach9107. Sometimes on Libera IRC as Jach or Jachy. Hit me up if you want to chat about anything; I'm bad at reaching out myself.
I'm just some unimportant programmer, dabbler by nature, you probably shouldn't listen to me but that doesn't always stop me from commenting or giving advice.
Please excuse my Common Lisp evangelism, I'm just a poser who after a journey from PHP through Java, Python, C, C++, JS, Scheme, and Clojure, just loves the language, enjoys programming anything in it, and has fun trying to correct decades-old misconceptions about it. Maybe by the time I'm 50 I'll have more to show for it. i.e. I think it's better to write cool programs in Lisp than to evangelize, and I'm failing to live up to my own standards.
I've collected some stuff I occasionally link in comments here in one place: https://www.thejach.com/view/2022/1/thoughts_on_writings_on_lisp
I'm not currently employed -- I did a speed-run to quasi-retirement in 2020.
I have a BS Degree in Computer Engineering from a school more renowned for degrees in video game [engine] programming.
I'm located in Bellevue, WA, feel free to get in touch for coffee/dinner/whatever if you're in the Seattle area. Or even if you're not, maybe we can play some online games. I like to visit Japan, too. でも、私の日本語は下手です。
(let ((files (coerce "ABCDEFGH" 'list))
(ranks (loop for r from 1 to 9 collect r)))
[(format nil "~a~a" file rank) (file <- files) (rank <- ranks)])
Based on the list comprehension macro from 1991 in https://3e8.org/pub/scheme/doc/lisp-pointers/v4i2/p16-lapalm... that still works. (defmacro comp ((e &rest qs) l2)
(if (null qs) `(cons ,e ,l2) ; rule A
(let ((q1 (car qs))
(q (cdr qs)))
(if (not (eq (cadr q1) '<-)) ; a generator?
`(if ,q1 (comp (,e ,@q) ,l2) ,l2) ; rule B
(let ((v (car q1)) ; rule C
(l1 (third q1))
(h (gentemp "H-"))
(us (gentemp "US-"))
(us1 (gentemp "US1-")))
`(labels ((,h (,us) ; corresponds to a letrec
(if (null ,us) ,l2
(let ((,v (car ,us))
(,us1 (cdr ,us)))
(comp (,e ,@q) (,h ,us1))))))
(,h ,l1)))))))
(defun open-bracket (stream ch)
(do ((l nil)
(c (read stream t nil t)(read stream t nil t)))
((eq c '|]|) `(comp ,(reverse l) ()))
(push c l)))
(defun closing-bracket (stream ch) '|]|)
(set-macro-character #\[ #'open-bracket)
(set-macro-character #\] #'closing-bracket)
Supports filtering too, e.g. (let ((xs '(1 2 3 4))
(ys '(1 2 3 4)))
[(+ x y) (x <- xs) (y <- ys) (evenp x) (oddp y)])
; -> (3 5 5 7)A weaker claim is that maybe there's no single silver bullet but there have been many bronze bullets that add up to more than a single silver bullet. I readily endorse at least that and think it's rather easy to justify.
I think part of it is that if there isn't a regular and practiced process for bumping standards, then gaps between revisions can grow quite large and stagnation can set in, and if there are any significant improvements it'll take longer for them to come to fruition than if there were regular revisions that are only modest most of the time. Looking at a few other things that come to mind: USB had an 8 year gap between 2 and 3 as well, PCIe had a 7 year gap between 3 and 4 (albeit while they only had a 3 year gap between the specification for 5 to 6, it still took 3 more years (2025) for the first pcie6 devices, and I still can't buy a consumer-level pcie6 motherboard, it's a separate mess), C++ had an 8 year gap between C++03 and C++11, Java had a 5 year gap between 6 and 7 (and another 3 years after 7 to get to Java 8); all of these things now have more rapid cycles.
Dependency hell comes for everyone, win32 may be stable but the broader ecosystem for Windows is little better than anything else. I say little because at least MS does still commit to a lot of backwards compatibility and ensuring some very old DLLs are still part of new Windows 11 installs.
As another comment notes some older Humble Bundle linux builds just don't work anymore on modern systems; some of those are just because they assumed a particular libjpg or libxml or whatever would be part of the base distro install and be around indefinitely. Bad assumption. But fixable the same way as missing DLLs from Windows builds.
If Dotcl does have good performance, it would be interesting to try running Coalton on top of it too. Coalton syntax is probably not unusual if you are familiar with OCaml and F#: https://github.com/coalton-lang/coalton (Though I'd expect the performance of the typical use case of running on top of SBCL to still be better.)
From the same project there's the recently released mine editor that's trying to be a friendlier gateway into trying Common Lisp (and/or Coalton) than emacs: https://coalton-lang.github.io/mine/ Time-to-first-SHOUTING is still once you start a REPL though -- it tells you that your package (namespace) is CL-USER. I sort of think it's one of those things that grows on you, or at least isn't annoying after a while (until you need to deal with certain foreign function interfaces anyway), and it's an interesting possible convention to use SHOUT-CASE in docstrings to call out specific parameters or other function names instead of some @param, \param, @link, or what have you.
But the nicest part is that once you connect to the production application, apart from network lag it's no different than if you were developing and debugging locally on similarly specced hardware to the server, you have all the same tools. Many of the broader activities around "debugging" don't need to happen in a paused thread that was entered with an explicit breakpoint or error, they can happen in a separate thread entirely. You connect, then you can start inspecting (even modifying) any global state, you can define new variables, you can inspect objects, you can define new functions to test hypotheses, redefine existing functions... if you want all requests to pause until you're done, you can make it so. Or if you want to temporarily redirect all requests to some maintenance page, you can make that so instead. A simple thing I like doing sometimes when developing locally (and I could do it on a production binary too) is to define some (namespaced) global variable and redefine a singly-dispatched method to set it to the self object (possibly conditionally), and once I have it I might redefine the method again to have that bit commented out just so I know it won't change underneath me. Alternatively I can (and sometimes do) instead set this where the object is created. Then I have a nice variable independent of any stack frames that I can inspect, pass to other method calls, change properties of, whatever, at my leisure without really impacting the rest of the program's running operation. Another neat trick is being able to dynamically add/remove inherited mixin superclasses to some class, and when you do that it automatically impacts all existing objects of that class as well. Mixin classes are characterized by having aspect-oriented methods associated with them; you can define custom :before, :after, or :around methods independent of the primary method that gets called for some object.
Unfortunately JRebel has killed their free tier, so I'd now point unwilling-to-pay programmers to something like https://github.com/JetBrains/JetBrainsRuntime which is IntelliJ/Eclipse/whatever-independent. I haven't tried it myself yet though... Given they only address the biggest class reloading concerns, I doubt it's actually comparable to JRebel for business-world Java. JRebel handles among other things dynamic reloading from XML changes and reinitializing autowired Spring beans that other classes use for dependencies.
*Caveat, I've been out of the professional Java grind for a while, I'd be pleasantly surprised if some new version that's come out contradicts me.
(I submitted this the day before but I guess this is HN's second-chance feature kicking in.)
I've been kicking the tires with mine a little bit yesterday and today, I think it's quite good for the beginner experience. But I'm constantly of two minds about reporting some feature requests. The project's primary goal seems to be existing as a stepping stone to even see what Lisp (and especially Coalton) is really all about before "graduating" to something like emacs, it feels like a secondary goal (though it is mentioned as a goal) to be usable by professionals as well, but there's inherent tension there. That's also been a weakness with the other editors: anyone already comfortable with Lisp development, professional or not, in emacs or not, isn't very likely to give the time of day to some new thing that's almost certainly not going to be as good as what they're used to. And so the new thing doesn't get the attention and feedback from experienced developers and the gap never closes.
In the startup world something like "every emailed spreadsheet is a business" used to be a motivating phrase, it must be more rough out there when LLMs can business-ify so many spreadsheet processes (whether it's necessary for the business yet or not). And of course with this sort of tool in particular, more eyes seeing "we're paying $x/mo for this service?" naturally leads to "can't we just use our $y/mo LLM to make our own version?". Not sure I'd want to be in small-time b2b right now.
But yes, treat it as a job, and make time for "fun time" after work at home using your favorite tools and languages and OSes, and you can still be happy, especially because the bills are being paid. And even in restrictive corporations there still may be opportunities to introduce a little of your favorite thing... Clojure itself "snuck in" at a lot of places because it was just another jar, and it's not too hard to shim in a bit of Java code that then turns things over to the Clojure system. You can also try getting away with internal-only tooling.
If I had stayed at my last job a little longer I would have tried putting more effort into sneaking Common Lisp in. I had a few slackbot tools I wrote in Lisp running on my machine that I turned over (with pre-built binaries) to someone else when I left (but I doubt they're running still). The main application was Java, and there was already mandates from security people and others not to use other JVM languages.. at least in application code. I was thinking (and got a prototype working) of sneaking in Lisp via ABCL, but only for selenium web driver tests. It was a neat trick to show some coworkers a difference in workflow: write or edit a web driver test, one of your asserts fails or an action click on some ID that's not there fails, in Java you get an exception and it's over, you'll have to restart the whole thing, which for us was expensive because these test suites typically spun up huge sets of state before starting. But with Lisp, exceptions don't automatically go back up the stack, but pause at the spot they occurred: from there you can do anything in the debugging REPL that you can do normally, redefine functions, classes, whatever, and then resume the computation from where it left off (or at some higher part of the call tree stack if you prefer), thus no expensive restart.
There's also ways to introduce things you like that aren't just different languages. My team started doing "lunch and learns" at most once a week (sometimes less often); if anyone wanted to talk about whatever for 30-60+ mins during a lunch period we'd get together and do it. Sometimes that would be about specific work things being built, sometimes it would be about external stuff, ideas (e.g. the Mikado Method) or tools. Once I did a brief presentation about property testing and later on got the quicktheories library for Java into the codebase and handling some tests, and ended up not being the only one to occasionally make use of it.
If students sign up and pay for a class you teach called "Data Structures & Algorithms", and you just read from Hamming's book every lecture and don't actually attempt to teach any data structures and algorithms, expect to not have a teaching job for long.