
Robpike/Lisp: Toy Lisp 1.5 interpreter in Go - mastabadtomm
https://github.com/robpike/lisp
======
kitd
_It is a pedagogical experiment to see just how well the interpreter (actually
EVALQOUTE /APPLY) defined on page 13 of that book really works. The answer is:
perfectly, of course._

Well timed! I've just been trying this out myself after reading Maxwell's
Equations of Software [1]. It's fun realising that the "base" functions you're
doing in Go can be reduced to even more fundamental forms using the Lisp
you're implementing.

[1] [http://www.righto.com/2008/07/maxwells-equations-of-
software...](http://www.righto.com/2008/07/maxwells-equations-of-software-
examined.html)

~~~
kazinator
Well-timed for you, but Pike could have benefited from doing this
experimentation forty years earlier. Who knows where he would be now?

~~~
rsc
I spoke to Rob shortly after he wrote this code (a few weeks ago). He said he
had been wanting to do some coding over the weekend and pulled his copy of the
LISP 1.5 Programmer's Manual - which he bought long ago, in grad school I
think he said - off his shelf, and this code was the result. He also talked
about how reading that book back when he bought it was incredibly eye-opening
at the time and had such a significant influence on almost everything he's
done in his career.

When I look through what Rob has done, I see a few patterns clearly traced to
LISP, including a strong emphasis on recursive approaches and simplification
by relying on functional (immutable) semantics. Look at the bitmap layers
work, Newsqueak, Sawzall. There is of course also a heavy dose of pragmatism,
which seems to be what you are criticizing.

Your question is therefore answerable: since he was in fact exposed to all
that material forty years ago, he would be exactly where he ended up.

Not that there's much type theory in that book, but I'll note that Rob has
also worked with type theorists in the past as well, notably Luca Cardelli on
Squeak. And he was the one who suggested we approach Phil Wadler to help us
work on the semantics of generics in Go. So again the ignorance about
programming languages that your comment implies simply isn't accurate.

This pattern of assuming what other people know about is endemic on HN (and
the internet more broadly, but especially so here), and it is harmful to
productive discussion.

~~~
chubot
It's not worth responding to people like that, but it's funny now to point out
that Wadler says in the Featherweight Go video that Go's type system has
something that Haskell's type system may like to borrow, i.e. that interface
types are open:

[https://www.youtube.com/watch?v=Dq0WFigax_c](https://www.youtube.com/watch?v=Dq0WFigax_c)

It's somewhere in the first 10-20 minutes (if someone wants to give a
timestamp link)

(Also, my experience is that such comments usually get downvoted/flagged on
HN, you just have to wait awhile)

~~~
tome
I'm not sure what Philip meant there, but I don't see this as a particularly
notable weakness of Haskell's type class system. We can already encode an open
world through the standard "HasX"-style classes. There's a good reason that we
don't use them much, though: type classes are typically not just interfaces
but come with laws expressing how they should behave. It's not clear to me how
those laws interact with an open world.

------
klodolph
Interesting:
[https://github.com/robpike/lisp/blob/master/lisp1_5/parse.go...](https://github.com/robpike/lisp/blob/master/lisp1_5/parse.go#L24)

    
    
        // Expr represents an arbitrary expression.
        type Expr struct {
            // An Expr is either an atom, with atom set, or a list, with car and cdr set.
            // Car and cdr can be nil (empty) even if atom is nil.
            car  *Expr
            atom *token
            cdr  *Expr
        }
    

Instead of using interface types for expressions, there is a simple expression
structure type with fields that may or may not be set depending on what type
of value it is.

I probably never would have done it that way, I would probably use an
interface type instead. This way actually saves space in cons objects… in the
above version, a cons is 24 bytes, and below it would be 32:

    
    
        type Expr interface { }
        type Cons struct { car, cdr Expr }
    

Not efficient compared to modern Lisp interpretations, but an interesting
choice.

~~~
brandonbloom
I find this a fascinating topic, so forgive me for this reply which has turned
in to a mini blog post of sorts...

Go embraces the common insight that you can encode a sum as a dependent
product. That is, here, you have a discriminated union of atoms and cons
cells, but the discriminant is not a specific tag value, as it would be in
Haskell or ML or similar, but rather the nil/non-nil state of these fields. Of
course, there is no static enforcement of this property, as there would be in
contemporary languages with sum types, including Rust's enums.

The quintessential example of this in Go is result/error multi-value returns.
The result is valid if the error is nil. If your goal is to save bits (which
is rarely the case for Go programs), as you note, avoiding an explicit
discriminant (such as an interface pointer) means that you're encoding the
discriminant's information in to less space, utilizing the specific domain
knowledge of mutual exclusion.

Stepping further from Go, this same principle is at play in Clojure, which
favors open maps for information. In Clojure, multimethods can dispatch on
arbitrary functions of values. This means that any field can easily be used as
a discriminant. Now, saving bytes is certainly not the purpose of this in
Clojure, but it's interesting to think about. Forcing dispatch in to a
privileged discriminant tag means that data needs to be transformed/parsed in
order to re-arrange information in to the tag for dispatch.

Alexis King wrote an excellent article about this sort of transformation:
[https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-
va...](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)

As excellent as that article is, I think the truth lies in some balance. Both
parsing and validation are useful techniques. Contemporary typed languages
tend to push you down a path towards parsing instead of validation, which is
probably the way the pendulum should swing for many use cases. However, there
is one language that stands in stark contrast: TypeScript. Because it needs to
support existing JavaScript idioms, it has grown a type system powerful enough
to enable reasonable type safety with a validation based approach. Tools like
control-flow sensitive type checking, literal types, and type-guards make that
possible. You can use an arbitrary key in some JS object with a literal type
value as a discriminant and the type system will do the right thing with union
types.

There is one other area where Go is interesting here: zero values. Go zero-
initializes all freshly allocated memory and encourages a style that embraces
that. These freshly allocated objects are called "zero values" and often they
are useful right out of the gate. You're encouraged to make code no-op safely
with zero values, or apply some sane defaults. This makes an important
distinction between `EnableFoo bool` and `DisableFoo bool`. Looping zero times
is a perfectly valid thing to do. Silently skipping nil values is a perfectly
valid thing to do in some cases. Etc. Clojure is similar with nil punning.
While not without it's downsides, this is an interesting point in the design
space that seems totally ignored by theoreticians and totally under-
appreciated by working programmers, even those who work with Go and Clojure
regularly. I'd really like to see that change, as I've found my programs have
generally improved as I make judicious use of these techniques.

~~~
setpatchaddress
Objective-C zero-initializes ivars, and it's common to rely on this. The big
problem is that you still need to test that code is doing the right thing when
it encounters a nil state.

If you define away the zero states completely with sum types, code flow is
completely accounted for at compile time, and entire categories of "oops, that
shouldn't be nil right now" bugs simply don't exist. On Apple platforms, this
is a major reason to use Swift instead of Objective-C.

I haven't used Go, only read some code occasionally; perhaps there's some
other difference here that makes this a nonissue. But it sure looks like it
has the same set of problems.

~~~
brandonbloom
It depends on whether or not you view "oops, that shouldn't be nil right now"
as a categorically different problem than "oops, this integer shouldn't be
greater than 10 right now" problems. Or "oops, this array shouldn't have an
odd number of elements right now" problems.

Yes, it's nice to have the type system catch problems. And yes, in the context
of memory-unsafe languages null or wild pointers are a big problem. But I've
found that you'd need a combinatoric explosion of data constructors and
abstract interfaces to enforce the interesting invariants of my programs. A
nil pointer (which panics at runtime with a good stack trace!) tends to be
among the easiest problems I have to solve when my programs violate
invariants.

I'm not arguing that sum-types are a bad idea. Only that dependent products
(with or without enforcement, static or dynamic) are an under appreciated
technique. Sum-types are really just one special case of dependent products.

------
mseepgood
He also implemented an APL-like interpreter in Go:
[https://github.com/robpike/ivy](https://github.com/robpike/ivy)

~~~
smabie
Personally, I find that one much more interesting. Before I was familiar with
array languages, I hoped that functional programming and lisp would become the
new zeitgeist of software development. Now, I think that array languages have
the most promise in revolutionizing the discipline. While it probably wont
happen, I wouldn't complain if array languages became the defacto norm for
most new applications.

~~~
G4BB3R
Is it a deeper rabbit hole than FP? Could you give some links or good array
language names?

~~~
4ad
APL, K, J.

Game of life in APL:
[https://www.youtube.com/watch?v=a9xAKttWgP4](https://www.youtube.com/watch?v=a9xAKttWgP4)

~~~
zerr
How about some CRUD app examples? :)

~~~
grovehaw
Dyalog is an APL vendor, they also offer a non-commercial license and high
quality documentation. They have a case study of a school management system
that is a record keeping system [0]. [0] [https://www.dyalog.com/case-
studies/education.htm](https://www.dyalog.com/case-studies/education.htm)

~~~
zerr
Thanks! Btw, what would be the open source alternative (of APL) that is
popular and used in production?

~~~
jetti
There is a GNU implementation of APL
([https://www.gnu.org/software/apl/](https://www.gnu.org/software/apl/)). Not
sure if it is used in commercial applications or not. I would imagine that if
you are doing commercial work with APL you are going to be paying for Dyalog.

~~~
zerr
> you are going to be paying for Dyalog

Not really. As I see APL is ISO standardized and there are many
implementations.

~~~
jetti
There are many implementations but not as many that are active (here is a
brief list
[https://en.wikipedia.org/wiki/APL_(programming_language)#Mod...](https://en.wikipedia.org/wiki/APL_\(programming_language\)#Modern_implementations)).
Dyalog provides more than just a interpreter/compiler but an entire
development environment.

------
chewxy
I hope one day when I grow old I get to engage in fun pasttimes like what Rob
did.

~~~
melling
Balance is important too.

He won a Silver Medal for archery in the Olympics.

~~~
dchest
...using Lisp's parentheses as a bow.

(Hint: look up 1980 Olympics)

~~~
newswasboring
I hated you for doing this and myself more for actually falling down that
rabbit hole. The explanation is here[1].

tldr: he is canadian and 1980 summer Olympics was boycotted by both canada and
america.

[1] [https://wiki.c2.com/?RobPike](https://wiki.c2.com/?RobPike)

~~~
kazinator
My only memory is of the closing ceremony: a fleeting image of a bear mascot
being carried across the stadium by balloons. I watched that on TV as a nine-
year-old boy in Slovakia..

------
mseepgood
He mentions "pedagogical" and "teaching tool". Who is he teaching? I would
love to be his student.

~~~
jascii
I read that more in a context of "teaching oneself". However, I think he's at
google atm: [http://herpolhode.com/rob/](http://herpolhode.com/rob/)

~~~
mseepgood
I thought that maybe he is teaching Google employees.

~~~
monocasa
I'm pretty sure it's a small project to teach himself.

~~~
mseepgood
Maybe, but the phrasing is "I plan to use this as a teaching tool" and not "I
did this as a learning experience".

------
wejick
He wrote unit test +1000

------
alrs
GOMODULE111, who needs it? :)

~~~
gauchojs
I had to "go mod init" before go build worked.

~~~
majewsky
Without a go.mod file, it will only compile when placed in the right place in
the GOPATH, which Rob Pike presumably does.

------
moron4hire
I read the title as "Robopike/Lisp" at first and suddenly imagined this was
some operating system slashfic set in Paul Verhoven film universe.

