I think that's a badly posed question. The main benefits of using Lisp are its introspection and interactivity. You can use the REPL to inspect your message bus at runtime, you can modify parts of it as it runs, you can interactively debug errors as they happen, but none of that is directly related to "how to implement a message bus".
I think that is more of an ADS question that it is a Lisp question; an efficient message bus, no matter whether you implement it in Lisp or in C or in Haskell, needs some synchronization primitives from the programming language core that are likely backed by OS primitives, as well as some data structures that need to be cleverly designed to be fast. Neither of these is language-specific - only means of implementing and using those might be language-specific.
In other words: Lisp makes it possible to be more productive by means of dramatically reducing the time of feedback loop and by means of providing in-depth language introspection, but it doesn't solve the problem of actually needing to figure out how to design programs and data structures. That's why e.g. SICP is not a Scheme tutorial, despite using Scheme throughout the whole book.
What exactly do you mean by "uninteresting"?
> how to efficiently implement a message bus with observers
You say:
> I think that's a badly posed question
But it isn't! It's the specific thing that person is trying to do and is having trouble with, the act of translating structures and designs they understand from other languages into the Lisp language.
This is the barrier to Lisp adoption. You can cite iterative development and live debugging all you want, but they only benefit after you've actually written a program, which is the barrier the potential users are struggling at.
No, you miss the point. There is little benefit in interactive and incremental development once a program is written. The very benefit that this such incrementality provides is noticeable during the process of writing the program, not after it; when a program is written, it's written, and from some point it doesn't really matter if you've done it by mutating an image-based programming language or by linking together compiled C objects.
For the logic in my iOS game I started out with a straighforward imperative system based on objects and explicit state, which quickly devolved in a big mess of if-then-else-but-only-if spaghetti code that was impossible to maintain. I refactored that to a reactive system based on what I call an 'event graph' where each node performs some kind of filtering or processing. For example nodes like 'filter on event type', 'take first 4 events', 'complete when event X received', etc. Game logic is implemented by branching off (subscribing) to nodes, synchronizing stuff by means of attaching a temporary subgraph that waits for a 'completion event' etc. Like I said it's quite similar to ReactiveX but simpler.
The basic concepts behind this are sound, but my feeling is that I need way too much code in to implement something like this in Lua, compared to what I would expect in something like Lisp, which seems more naturally suited for this kind of task. I just find it hard to get started trying to map the solution I already have to a Lisp language, because most of the 'introduction to Lisp'-like resources are so focused on artificial examples that are mostly interesting from a computation-theoretic point of view.
(see tutorial: http://stefano.dissegna.me/cells-tutorial.html, and documentation: https://gitlab.common-lisp.net/cells/cells/-/tree/master/doc)
(ql:quickload :cells)
(defpackage :robot (:use :cl :cells))
(in-package :robot)
Define a robot model (class) where command in an input, and velocity is defined by an update rule: (defmodel robot ()
((command :accessor command :initform (c-in nil))
(velocity :initform (c? (case (command self)
(:left -10)
(:right 10)
(t 0))))))
Whenever command is modified, velocity is updated accordingly. You can add observers for slot changes: (defun log-change (what from to)
(print `(:change ,what :from ,from :to ,to)
*debug-io*))
(defobserver velocity ((model robot) new old boundp)
(when boundp
(log-change `(velocity ,model) old new)))
Then, if you instanciate the model, and mutate the command slot: (let ((w (make-instance 'robot)))
(setf (command w) :right)
(setf (command w) :left)
(setf (command w) nil))
The following is logged: (:CHANGE (VELOCITY #<ROBOT {1015D66783}>) :FROM 0 :TO 10)
(:CHANGE (VELOCITY #<ROBOT {1015D66783}>) :FROM 10 :TO -10)
(:CHANGE (VELOCITY #<ROBOT {1015D66783}>) :FROM -10 :TO 0)
Anyway, the book Common Lisp Recipes (E. Weitz) is good for solving actual, pratical problems with Lisp.Where Lisp and Lua differ is that Lisps are infinitely more malleable as languages. You can more-or-less invent new syntax to assist in your problem domain. For example, if you find that constructing your 'event nodes' requires boilerplate in Lua, you can write Lisp macros that make things clear again. Or you might write a little macro DSL to make constructing your graphs easier.
I enjoy Lua, but I constantly pine for the flexibility of a Lisp when Lua's clunky syntax annoys me. I find Fennel is a good compromise between the two - especially if you already have Lua code that you want to port over.
I suggest using portacle (a preconfigured emacs) as development environment during that course as this is the easiest way to get cracking in CL imho.
Although Janet's core library is different from CL, I think once you grok LISP, it's not hard to switch to another dialect: After working through ~half of PCL I abandoned it in favour of a learning-by-doing approach in ClojureCLR as I work mostly in .NET or Mono atm.
There are good references and snippets on the Cookbook: https://lispcookbook.github.io/cl-cookbook/
for libraries, see https://github.com/CodyReichert/awesome-cl (and see the lparallel or lfarm libraries, channels, cl-gserver, the built-in SBCL threading functions, etc)
Also note that CL is not purely functional, you can throw in FP or OOP or iterative code.
I still think that Winston & Horn's classic Lisp (3rd ed.) conveys these ideas very well. SICP is also very good at it, but it's based on Scheme.
https://lisp-lang.org/learn/getting-started/
You can start here to set up your IDE and then read the "Practical Common Lisp" book.
TLDR: Guile is very easy to learn
The main thing holding me back to try to replace it with something Lisp-like is that I have zero experience in Lisp programming. I know some FP theory (in fact the Lua-based event system lends some concepts from it), but when I try to reason about common subproblems I really have no idea how to map them to something like Lisp. For example how to efficiently implement a message bus with observers that can register themselves, using state-based functions to execute when certain events are received, scheduling events, async/await to suspend processing until some event is received, etc.
Does anyone have pointers to books or websites to learn 'real-world' LISP programming? In other words not yet another introduction that shows the same old forced examples that just show you how to evaluate an expression or use higher-order functions to solve artificial toy problems, but resources that teach you how to be productive in a Lisp language?