Why Lisp beats all languages

| Lisp, Racket | By: Kevin R. Stravers

This is a tough essay to write. Every time I visit hacker news or read anything new about development I get frustrated and annoyed by the ignorance that prevails in software engineering. Why? Because people are not only reinventing the wheel, but making small languages/frameworks to satisfy their domain needs. I say it’s a waste of effort to do so. Do we really need another framework on top of JS to do one thing better? A framework that won’t be composable with other frameworks. It’s another metaphoric monkey-wrench in the machine. And what about languages? "Hey guys I made a new language called Leaf", "Hey folks I’ve made a new language called Loop", "Hey people ..."... All this effort is wasted. Why?

Because these languages make it hard to interoperate with each other. Their datastructures are wildly different in memory. Their runtime requirements and implementations differ. And their modes of handling memory differ. Now we have a problem. Wanna talk to another language? Just fire up 2 interpreters and let them talk over a socket or pipe. But that provides another problem: creating a communication language over that socket.

Ugh. I tell myself. UUUUUGH. WHY?!

We all know software devs who work on this are pretty smart. They’re driven. They have ambition. Maybe you’re one of them. But I’m telling you that you’re throwing most of your work and time right out the window by making languages and frameworks.

why?! you ask

because there’s already a solution and it allows you to communicate seamlessly among languages, without manufacturing difficult protocols.

PEOPLE! IT’S LISP. And I’m not joking around here. BUT WHY is the question. Because of macros. They allow us to construct little languages inside of lisp that perfectly capture whatever domain you’ve got.

There’s ABSOLUTELY no need to construct another language/interpreter/compiler. You can do it in Lisp and it will be nice. And you can talk to other little languages. And the code is debt-free, hell, even "design patterns" (which are honestly just a way around a language due to limitations) aren’t needed in Lisp.

One of the most useful things about macros in Lisp is that they’re contextually aware. Just look:

could used ’typed/racket/base

> (require 'logger)
> (logger-color #f)
> (trce (+ 1 2))

(trce (+ 1 2) = 3)

3

So (trce (+ 1 2)) prints (trce (+ 1 2) = 3) (The red in the above is what’s printed to standard error). Imagine what we gotta do in nearly all other languages: print ("1 + 2 = " ,1 + 2) substitute numbers with variables.

THAT JUST SUCKS. WHY do I need to manually write what I’m printing if it’s ALREADY in the expression?!

AntiLisper: "Alright, is that the ONLY thing you can show me? Ugh I HATE parentheses. I bet your STUPID logger is the ONLY thing you’ve got going for ya.

Listen here you little shit. You’ll get used to the damn parentheses once you realize that Lisp is the greatest fucking language in existence.

You know what’s cool? We can embed HTML in Lisp:

> (require web-server/http/xexpr)
> (response/xexpr
    #:preamble
    #"<!DOCTYPE html>"
    `(html (head (meta ((charset "UTF-8"))))))

(response 200 #"Okay" 1550584449 #"text/html; charset=utf-8" '()     #<procedure:...r/http/xexpr.rkt:25:3>)

Obviously I’m not gonna include a bunch of body stuff but you get the idea. Is that all? Fuck no boy, there’s a shit tonne to come, ever heard of list-let? No because I just invented it.

> (list-let '(1 2) (a b) (+ a b))

3

Yeah, it destructures a list into variables. This is not something you can do with a function since it requires bindings in the calling scope.

AntiLisper: "WHY do you need that? Hellooooooo... I bet you don’t even code real shit."

Well Helooooo we remove dirty, disgusting duplicate code noise by using this, I mean just imagine mapping lists of lists (counting total number of students in a grade of men and women):

> (map
    (lambda (x) (list-let x (men women) (+ men women)))
    '((31 28) (20 15) (83 67)))

'(59 35 150)

AntiLisper: "Oh yeah? That’s dumb, I can do that using a structure and besides I can just use a let myself. Why would I need list-let? Seems redundant."

Dude seriously look at the equivalent let-version of the code:

> (map
    (lambda (x) (let ((men (first x)) (women (second x))) (+ men women)))
    '((31 28) (20 15) (83 67)))

'(59 35 150)

WAY MORE NOISY! And besides we can do EVEN BETTER by removing the redundant lambda (x) part:

> (map (lambda-list-let (men women) (+ men women))
    '((31 28) (20 15) (83 67)))

'(59 35 150)

Look. Code noise plain sucks. Yeah sure you can do everything with lambdas and lets and what-not but it’s gonna look noisy, ugly, and you’ll spend more time reading and understanding the code. Besides, once you use macros to clean up common patterns you’ll see that your code becomes much cleaner and much more concise overall. This DOESN’T MEAN the code becomes hard-to-read. NO! Because a small code SIZE does not imply it’s hard to read. Maybe terse languages without macros are hard to read. But using macros makes the code EASIER to read simply because all macros remove noise that’d normally obstruct YOUR understanding.

AntiLisper: Also [My language of choice] already has this implemented via pattern matching. Checkmate.

Don’t you get it? WE made the freaking pattern-matching/destructuring by implementing OUR OWN new syntax. It’s not from some pre-existing pattern matching that’s builtin. WE made it. Can you image what else you can build WITHOUT having to change language for every new breakthrough in language design?! Just imagine what could have been: instead of moving from C to C++ you’d just tack on an object-oriented macro in Lisp. Instead of moving to Haskell you tack on a macro that ensures purity and type safety. You can get all that whilst still able to use all your previous code because it’s in the same base language.

In fact let’s look at a practical example that I use on my website gondola.stravers.net here I’ve defined a macro called timemo and define/timemo which memoize a result from a procedure for a set amount of time before recomputing the result. This is useful when a bunch of data are to be loaded from disk which is often accessed by the website. Loading the data for each request is terribly expensive so we cache it for 60 seconds before refreshing. Here’s how the original code is: (define (list-all) (heavy-operations-here)) now look at the memoized code (define/timemo list-all (60) (heavy-operations-here)) see how it’s ALL declarative and clear? 60 is the refresh-time in seconds. NOTHING MORE is needed! THIS is the POWER of lisp people.

AntiLisper: Uhhh, what’s with (60) and not 60? Stupid parentheses again, HA!

It’s used to group memoization-related parameters, there’s more than just seconds to take care of, and this allows us to add options as the complexity grows. So maybe it becomes (60 30) for whatever reason.

AntiLisper: Ok well guess what. I can do all this with channels and a few threads. Memoization is EASY and you’ve proven NOTHING.

This is exactly what the macro expands into. A bunch of channels and threads. The only difference is the macro code is minimal, clean, and states only what it needs while you’re copying code all over the place when you do memoization on multiple procedures. This directness and non-repeat-yourself idea of Lisp is what gives it its colossal power. It’s what most languages strive to be.

In the end we actually see many languages converging to Lisp. Hell even C++ is getting "macros" (although they’re limited and not called that), Python with its decorators. Template Haskell... They need these features out of ergonomic necessity. It’s a scientific inevitability. Lisp is simply the most malleable and headstrong of languages. It would serve you well to learn Lisp (preferably Racket).