Live data from Hacker News

Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

github.com

51–60 of 73 posts

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#51
post #4
post #3

Earlier quoted context omitted.

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.

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

jsoftware.com

- GPL license - Extremely well-documented - General purpose - Runs on any platform - 64-bit - C-like performance for many operators - Interpreted, interactive development environment - Package manager - Near-zero boilerplate

https://www.jsoftware.com/books/pdf/easyj.pdf

"Array language" describes a pretty generic syntax format wherein an operator acts on multiple inputs. Functional programming is a style that nests nicely with arrange languages. If you know "R," just think of lapply. It's like lapply everywhere, but without the lapply syntax. Every function is vectorized.

People who aren't formally trained on loops or object-oriented programming tend to pick this up more easily. That said, you can do OOP and loops in J if you want to.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#52
post #41

Earlier quoted context omitted.

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

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 signific…

> 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.

That's not really what my comment is about; and I know very well who Pike is, and I'm assuming everyone here does. (Come on, legendary!)

Note that the phrase I used, "doing the experimentation", specifically avoids speaking to what Pike knows or doesn't know.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#53

Interesting: https://github.com/robpike/lisp/blob/master/lisp1_5/parse.go... // 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 ma…

> I probably never would have done it that way, I would probably use an interface type instead I think when you're all in the same package and accessing unexported fields you know about, this is much clearer than an interface. I often add unexported mutually exclusive fields that local code has advanced knowledge about to prevent unnecessary over-abstraction.

And yeah, and then a new team member doesn't know certain fields are mutually exclusive, and neither does the compiler because of the lack of sum types.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#54
post #47
post #41

Earlier quoted context omitted.

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 signific…

I wish I could upvote this twice. The classic "blub" post by Paul Graham is usually read in "upwards" direction where people using a language without some abstractions (say, monads or borrow checkers) can't understand why they would need it when looking at a language that has them. There is probably also a "downwards" interpretation where people using such a language can no longer understand that it's possible to be…

> The classic "blub" post by Paul Graham is usually read in "upwards" direction where people using a language without some abstractions (say, monads or borrow checkers) can't understand why they would need it when looking at a language that has them. There is probably also a "downwards" interpretation where people using such a language can no longer understand that it's possible to be productive in languages that lack the advanced abstractions.

Thank you, this is a very helpful framing of something I've recently been struggling to articulate about languages with sophisticated type systems after having surprisingly positive and productive experiences with Clojure and Go.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#55
post #31
post #24

Earlier quoted context omitted.

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

There is a GNU implementation of 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.

> you are going to be paying for Dyalog

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

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#56
post #54
post #47

Earlier quoted context omitted.

I wish I could upvote this twice. The classic "blub" post by Paul Graham is usually read in "upwards" direction where people using a language without some abstractions (say, monads or borrow checkers) can't understand why they would need it when looking at a language that has them. There is probably also a "downwards" interpretation where people using such a language can no longer understand that it's possible to be…

> The classic "blub" post by Paul Graham is usually read in "upwards" direction where people using a language without some abstractions (say, monads or borrow checkers) can't understand why they would need it when looking at a language that has them. There is probably also a "downwards" interpretation where people using such a language can no longer understand that it's possible to be productive in languages that lac…

It looks a lot like a manifestation of incommensurability as observed by Kuhn:

https://en.wikipedia.org/wiki/Paradigm_shift#Incommensurabil...

This is exactly what we see. People adhering to different computing paradigms use some of the same words (e.g. "type"), but they have different meanings and basically they talk past each other as a result.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#57

Interesting: https://github.com/robpike/lisp/blob/master/lisp1_5/parse.go... // 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 ma…

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...

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.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#58

Interesting: https://github.com/robpike/lisp/blob/master/lisp1_5/parse.go... // 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 ma…

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,…

Also called ZII - Zero Is Initialization, contrasting with RAII.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#59
post #4

Earlier quoted context omitted.

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

Wolfram Language / Mathematica, MATLAB, R

No, not really, None of these are what I'm talking about when referring to "array languages". I'm talking about APL and its descendants, j, kdb+/q, k, etc.

Re: Robpike/Lisp: Toy Lisp 1.5 interpreter in Go

#60

Interesting: https://github.com/robpike/lisp/blob/master/lisp1_5/parse.go... // 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 ma…

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,…

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.

Post reply on HN