Hacker News new | ask | show | jobs
by jimbob45 21 days ago
I went back and forth with ChatGPT on this a while ago. I concluded that my main problem with Lisps (and C) is that types don’t have their core functions associated with them concretely. The only way to know strcat, puts, and strlen exist is…to know that they exist. This vexed me. We lightly mapped out the idea of having traits for types in lisp and then having those traits and their associations frozen at compile time. That way, you could see which traits a given type implements and feel your way around the language more confidently.

However, like “If you give a mouse a cookie”, I realized that once I had my traits that I also wanted compile-time attributes à la Rust/C# and there’s no clean way to add those to Lisp’s syntax without seriously cluttering it up (note: yes, function-level attributes would look fine but C# allows attributes on locals, iterators, and usings which just don’t mesh with Lisp’s syntax).

I gave up. Lisp is neat but it just doesn’t fit in the future of programming that Rust and C# have shown us.

3 comments

> The only way to know strcat, puts, and strlen exist is…to know that they exist.

Yes, and the only way to know that Vector and Hashtable exist is... to know that they exist.

> I also wanted compile-time attributes à la Rust/C# and there’s no clean way to add those to Lisp’s syntax without seriously cluttering it up

That's what declarations are for, a Lisp could have something like:

  (declare (type Foo bar)
           (derive Clone bar))
  (define bar
    (bar:from-string "Hello, World!"))
Or something like:

  (db:with-connection (con (db:get-connection my-database))
    (declare (db:read-only con))
    (db:query con
              :table "students"
              :where (> "score" 80)
              :sort-by "name"))
> Lisp is neat but it just doesn’t fit in the future of programming that Rust and C# have shown us.

C♯ and Rust are dead languages, you make a change and you have to recompile the entire program and restart it from scratch. You can't ask a running program things like 'how many users are connected at the moment?'. That isn't a future I would like to see.

You might be interested in the Gauche implementation of Scheme which overall has a Python-like extended standard library, notably including a lot of functions that are generic over sequences and collections which historically in Scheme (and even moreso in Lisp) had a menagerie of special-cased functions for working with them.
> types don’t have their core functions associated with them concretely.

They do in Racket? You look at the "List" documentation and you find all of the functions for lists. Likewise with hash tables, numbers, strings, sequences, files, etc.

> The only way to know strcat, puts, and strlen exist is…

... to look at the documentation on docs.racket-lang.org. The powerful search includes results not only for the standard ("batteries included") library reference, but also the standard guide and 3rd party libraries.

> once I had my traits that I also wanted compile-time attributes à la Rust/C# and there’s no clean way to add those to Lisp’s syntax

I mean, why are you not able to do (trait whatever) before (define whatever) or (for-each whatever) or (let whatever)?

> Lisp is neat but it just doesn’t fit in the future of programming that Rust and C#

Have you tried it or did you just try to figure out why you don't think you like it from an LLM?

As far as I know, Racket docs are entirely human-generated. The functions aren’t tied concretely together to types by anything. In C#, types and their member functions are automatically tied and inspectable in code. That’s what I feel is missing. Also, I don’t want to take away from the phenomenal Racket docs. They’re a real treat to work with.
If you're referring to C# (and Java) being the Kingdom of Nouns where a type like ArrayList is defined and contains its methods, sure, Lisp is not exactly like that, but I feel like conventions give you a similar experience. For example, functions related to hash tables all have `hash` in their name and are either constructors or they take a `hash` argument. They are contained in their own file (hash.rkt).

Also, doesn't inheritance interfere a bit with your "functions aren't tied concretely together to types" observation? You can examine a source file for ArrayList, but if it extends List, you may not see everything you expect to see, and would just defer to the docs to help you out (or click through more source files).

I assume Racket contracts can handle your concern about relating functions and types. An IDE or static analysis tool can help you find all of the functions that operate on a `dict` type if you don't want to rely on conventions where you just seek out a dict.rkt file.

For what it's worth, coming from lots of Java experience, as a Racket novice, I poked around the Racket internals and added Candlesticks to its `plot` library [0] and removed a call to a deprecated gdk function that was causing overhead when drawing text [1]. It never felt like an insurmountable task just because methods aren't defined within types.

By finding problems with Lisp, you are finding problems with s-expressions, which, to me, are so plainly superior to XML and JSON for defining data that I wish more languages would at least consider adopting them for data definitions.

[0] https://github.com/racket/plot/commit/7f38feaf6e28a1decec93d...

[1] https://github.com/racket/gui/pull/95

> I assume Racket contracts can handle your concern about relating functions and types

contracts are not real types enforced by the compiler/runtime. that means you need to keep them in sync manually and you dont get performance benefits from typed vm instructions. you also lose a big safety net that makes sure you always call a function that can handle the values you pass in. the whole industry has been moving towards ml style strong types for the last 10 years and its for a good reason