
Bone Lisp – Lisp Without Garbage Collection - spraak
https://github.com/wolfgangj/bone-lisp/
======
hkjgkjy
Since ulisp (lisp for Arduino) came up on HN the other day, I've been trying
to implement a lisp in C with garbage collection that will run on my x86
machine (your usual macbook).

It being my first ever "real" c program, I'm not quite there yet. Very curious
to read more source code of small languages, preferably things that are
implemented in one file only.

~~~
munificent
If you just want a GC, here's a tiny one I wrote:

[https://github.com/munificent/mark-sweep](https://github.com/munificent/mark-
sweep)

And an article I wrote about it:

[http://journal.stuffwithstuff.com/2013/12/08/babys-first-
gar...](http://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-
collector/)

This was the inspiration for uLisp's GC
([http://www.ulisp.com/show?1AWG](http://www.ulisp.com/show?1AWG)). :)

If you want a more full-featured but still small language in C, you could take
a look at Wren:

[http://wren.io/](http://wren.io/)

[https://github.com/munificent/wren](https://github.com/munificent/wren)

~~~
hkjgkjy
I am using your blog post about the mark and sweep-GC for my program! Thank
you for publishing it! Very informative, entertaining and to-the-point. Thank
you!

~~~
munificent
You're welcome! I'm glad you enjoyed it. It was a lot of fun to write.

------
michaelfeathers
This makes me wonder whether it would be worthwhile to design languages with
no garbage collection or manual deallocation. Non-stack memory can be
allocated but never freed, and memory exhaustion would be fatal.

We might be at the point where this is an acceptable simplification for very
fine grained processes and services. Has anyone explored this model?

~~~
kazinator
Just because memory can be freed doesn't mean it has to; the programmers can
choose not to.

The mode of not freeing has been explored in programs that are short-lived and
run on top of an OS that reliably cleans up memory. For instance, system
utilities and compilers and such.

Got a C program that crashes due to premature allocation, according to
Valgrind, and it is hard to figure out why? It it just a short-lived utility
that does some job and exits? Then just #define free(p) ((void) 0) and
rebuild.

~~~
PeCaN
Or because it's considerably faster—if you don't have to deallocate, you can
just reserve a bunch of pages on startup and use a stupid simple bump
allocator.

Actually, D's compiler more or less works this way:
[http://www.drdobbs.com/cpp/increasing-compiler-speed-by-
over...](http://www.drdobbs.com/cpp/increasing-compiler-speed-by-
over-75/240158941)

~~~
kazinator
I have seen a program, which meticulously freed everything on shutdown, take
over a minute to actually do so under a large and complex test case involving
lots of data. In production use, this would be a complete waste of CPU cycles.

~~~
ksherlock
Over a minute? At least it didn't take a few days...

[http://lists.gnu.org/archive/html/coreutils/2014-08/msg00012...](http://lists.gnu.org/archive/html/coreutils/2014-08/msg00012.html)

[https://news.ycombinator.com/item?id=8305283](https://news.ycombinator.com/item?id=8305283)

------
johnhattan
As someone who hasn't dealt with Lisp since the 1980's, help me out here.
Immutable structures and Lisp seems to be incompatible, as Lisp's lists are
about the most mutable things you'll ever see.

If, for example, I want to put a value into the middle of a list, would I be
doing something akin to. . .

MyList = MyList.firstHalf + NewValue + MyList.secondHalf?

And how doesn't this become a horrible bottleneck?

~~~
openasocket
You can represent the list as a zipper data structure[1]. The idea is to
represent a "cursor" pointing to some point in the list as a triple: (list of
points before, point at current position, list of points after). So a zipper
for an arbitrary list "xs" pointing to the beginning would be (() (car xs)
(cdr xs)). There are some things you can do to handle the case with empty
lists, which I'll leave to the reader :).

Given a zipper (as x bs) you can move the cursor right (rather, compute the
zipper with the cursor one step further to the right, since we're purely
functional) as ((cons x as) (car bs) (cdr bs)). You can insert a new element
"y" into you current position by computing (as y (cons x bs)). If you discard
the old version of the zipper every time you move or insert, this will use the
same amount of memory as just storing the list, and you get insertions in O(N)
time and O(1) memory. There's a whole field devoted to making data structures
like this for purely functional languages, and this zipper concept extends
itself rather naturally to trees.

[1][https://en.wikipedia.org/wiki/Zipper_%28data_structure%29](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29)

------
awt
Garbage collection should be in the hardware.

~~~
toolslive
Intel tried this in the early 80s:
[https://en.wikipedia.org/wiki/Intel_iAPX_432](https://en.wikipedia.org/wiki/Intel_iAPX_432)

~~~
adrianratnapala
Wow, the iAPX 432 sounds really cool. It contains a lot of ideas that I wish
had taken off. Pity it failed.

That failure might prove that those ideas were bad. But I think it just proves
that MS-DOS compatibility is really, really important.

Weep for humanity.

~~~
pjmlp
I always see those failures more as political than technical.

How mankind would be if there weren't a few lunatics that kept on trying to
fly and fail?

Just to cite one example from many.

~~~
awt
You are so right. One cannot succeed in technology outside of politics.

------
amelius
It would be cool if the garbage collector could be written in the language
itself.

~~~
openasocket
In this case a garbage collector is unnecessary. In this Lisp, list structures
are immutable, which means it is impossible to create a list with a cycle.
Thus, the interpreter can use basic reference counting without fear that
anything will leak.

~~~
pklausler
In Haskell, data are immutable and yet cyclic structures are possible.

    
    
      allOnes = 1 : allOnes

~~~
openasocket
This is possible in Haskell because it allows for lazy evaluation. In a strict
language, immutability means you can't form cycles.

~~~
pklausler
While non-strict evaluation helps with forming cyclic structures in general,
in this specific example it is not required. You just need a language that
allows names to appear in their bindings, such as in C:

    
    
      struct node { int x; struct node *next; } allOnesList = { 1, &allOnesList }, *allOnes = &allOnesList;

~~~
openasocket
Haha, I hadn't thought of that! That ability to create self-referential data
is the main factor. That isn't really possible in lisp without some sort of
letrec function, which this lisp shouldn't support. You would also have to
watch out for recursive function definitions. Usually, the function body will
just contain the symbol that refers to the function, which conceptually is
similar to a straight pointer to the function, and someone might try to
optimize it that way.

In fact, with more thought, I realized the lazy evaluation is actually
orthogonal to forming cyclic data. The "allOnes = 1 : allOnes" line doesn't
necessarily form a cycle (depending on the interpreter/compiler, I imagine ghc
and any other sensible implementation will form a cycle). Naively, this will
just create a thunk which will get repeatedly evaluated, generating a long
list of ones.

EDIT: Nevermind, you're right.

~~~
pklausler
Thunks don't get repeatedly evaluated, sorry. Just once at most.

