uLisp – Lisp for the Arduino
ulisp.com
ulisp.com
> The Remote Agent software, running on a custom port of Harlequin Common Lisp, flew aboard Deep Space 1 (DS1), the first mission of NASA's New Millennium program. Remote Agent controlled DS1 for two days in May of 1999. During that time we were able to debug and fix a race condition that had not shown up during ground testing. (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.) http://www.flownet.com/gat/jpl-lisp.html
So often in embedded development I resort to things that make printf-style debugging look downright virtuous. ("If you reach some complicated runtime state, blink the LED thrice.") Having a REPL would let you poke around without the compile-flash-test cycle and have an actual conversation with the hardware.
void markobject (object *obj) {
if (obj == NULL) return;
object* arg = car(obj);
if (marked(obj)) return;
int type = obj->type;
mark(obj);
if (type != SYMBOL && type != NUMBER) { // cons
markobject(arg);
markobject(cdr(obj)); // <--- TAIL CALL!
}
}
It's simple enough to obj = cdr(obj) and wrap a loop around this.That's why I wrote "in proportion to list length", not "tree structure depth" in general.
Above, we still have recursion when marking the arg = car(obj) value. However, Lisp lists tend to be greatly unbalanced in favor of growing in the cdr direction; that is why it helps to iterate on the cdr.
You can read the implementation... code:
I think this is an oversimplification. "Code is data" means several things:
1. Both use the same ASCII representation to humans (homoiconity)
2. Lisp Macros operate using data traversal functions
3. You can load and eval code on the fly
Really only #3 is invalidated under Arduino constraints. #1 is the source at rest and #2 is compile-time.
Moreover, 1-3 together are more than sum of its parts. The idea that "in Lisp code = data" doesn't mean that elsewhere it doesn't; code = data is a fundamental truth about the nature of information (that many other languages and their ecosystems did quite a lot of work to obscure). Lisp only makes exploiting that truth much easier than other languages.
Also, a nitpick. #2 is not "compile-time". Macros can and are expanded at what you'd call "run-time" as well. Generally, Lisp systems don't have "compile-time" and "run-time" you know from other languages; the split is usually read- / load- / compile- / run-time, with some other "times" sprinkled in (e.g. "macroexpansion-time"), and this split is not necessarily sequential - you can read/load/compile stuff in what would usually be called "runtime", and read/load/compile phases have full access to features of the running Lisp image, including changes you made previously - so, for example, code you're just compiling can, during its read-time, refer to variables you previously created at run-time and run computations on them.
So, I googled "Dijkstra's Algorithm in Lisp" and got this: http://richardsherriff.com/?p=233
Now, I'm no lisp expert, so I can't judge whether the author of this code actually knows what they're doing, but I know for sure that in an imperative language the implementation of the algorithm is much more concise and understandable.
I have concluded from my observations that claims of Lisp being "easier", "ideal for learning fundamental programming concepts" and "more expressive" are exaggerated.
> so I can't judge whether the author of this code actually knows what they're doing
Not really. He can't even format the code.
For example from that blog post:
(defun return_highers_helper (list element result)
(cond
((null list) result)
((equalp (first element) (first (first list)))
(cond
((< (rest element) (rest(car list))) (return_highers_helper (cdr list) element (cons (car list) result)))
(t (return_highers_helper (cdr list) element result))
))
(t (return_highers_helper (cdr list) element result))
))
This would be written usually as: (defun %return-highers (element list)
(remove-if-not (lambda (item)
(and (equalp (car element) (car item))
(< (cdr element) (cdr item))))
list))Lisp is capable of imperative programming.
https://github.com/gwkkwg/cl-graph/blob/master/dev/graph-alg...
or perhaps: https://planet.racket-lang.org/package-source/jaymccarthy/di...
I was a little surprised to not find any direct graph algorithms at rosetta code[1], but for showing off somewhat similar code, this might be of interest:
http://rosettacode.org/wiki/Longest_common_subsequence#Commo...
I don't really think common lisp is a great beginning language, compared to, say, python or ruby - although Racket Scheme is pretty nice. I did for example come across this introduction to A * that uses Python for the code examples:
http://www.redblobgames.com/pathfinding/a-star/introduction....
As for introduction to new programmers, Racket's tutorial is kind of nice, jumping right in to do some simple graphics with an embedded DSL:
https://docs.racket-lang.org/quick/
[1] WillPostForFood obviously out-searched me here, finding: http://rosettacode.org/wiki/Dijkstra%27s_algorithm Not sure how I managed to not find it - perhaps trusting search rather than trying to use the site-index would've helped...
2. Use it to invalidate any arbitrary claim X about the language.
3. Profit?
I wonder how predictable and controllable that is. Adding GC to what is likely to be a real-time device seems like a potential showstopper. But if the numbers can be a bit more firm, or if the application can receive alerts on pause / resume, that might not be such a big deal.
[1] http://michaelrbernste.in/2013/06/03/real-time-garbage-colle...
I've confirmed this by looking at the (surprisingly readable and small) code: http://www.ulisp.com/list?1BWZ
Also, it looks like the algorithm is mostly deterministic, so that's good from a real-time standpoint. And it only runs if memory is about to be exhausted, so if the application ensured that it stayed within the Arduino's memory budget, it's essentially a no-op.
This is the approach of e.g. Naughty Dog's old GOAL language: it isn't doing Lispy things in the engine code, so much as using Lisp as a very powerful macroassembler. This approach is potentially advantageous over having a batch process to compile and upload to the device since it presents some important groundwork for doing "live editing". The performance of the shipped application is effectively unlimited, since the user code has so much control over the resulting output.
The main downside is that the resulting app isn't designed for portability, being so oriented towards the machine code rather than an abstract layer: Getting the portability and the live-editing functionality and the performance is a considerably harder problem to visualize, but production C++ game engines are doing it now.
ulisp runs with 2 kbyte RAM.
That said, I just don't get statements like:
"It's also an ideal language for expressing complex ideas, such as teaching a robot to solve mazes or finding the shortest route on a map." More ideal than C, sure. But I don't see the huge advantage over other high level languages.