This is a first post in LR category–Language Review–where I will factually opine on various programming languages. And today’s victim is Lisp.

Or first, let me say the emperor has no clothes, or Scheme is an awful language.

I mean, not only it is an awful first language to teach beginners, or to self-learn on your own, but also in general, as a programming language in 2023, it is not as great as it’s cracked up to be. I will not go through all the other arguments made by others. Those are all (still) valid. I will approach this from a different angle, and given this angle, my rant applies to both Scheme and all other Lisps, including Common Lisp and Racket.

And that angle is the simple and useful and elegant aspects of readability and features. Programs are meant to be read more often than written, and as a programmer, I like to visually “see” my program in a way that matches how my brain “sees” the abstractness of the program. In other words, a programming language I will use to learn something should match how my brain works, rather than to force my brain to become like that language. Additionally, I would want my language to have all the standard features I need, right out of the box.

Whether a language become popular in the industry is directly based on this, in my opinion.

In this post I will only mention my first beef (regarding visual appeal and readability and conciseness) with Lisps including Scheme. Interestingly, my beef is not a mere rant as it is backed by a heavyweight like Wadler (Wadler, 1987) where he uses a simple example to deride Lisp’s visual appeal:

(define (sum xs)
  (if null? xs)
    0
      (+ (car xs) (sum cdr xs)))))

Phew!

All that ceremony to just do this:

(At the top right corner of this page, switch to dark mode in order to see the code-snippets more clearly.)

sum [] = 0 
sum (x:xs) = x + sum xs

Wasn’t that much better?

And:

som [] = 0
som (x:xs) = x + som xs

Renaming sum to som here to avoid conflict with a Haskell primitive named sum (Newbie tip: that is, Haskell already has a built-in function of this name.).

Exactly the same, right?

To fully illustrate the problem, let’s write a larger program to demonstrate how quickly you will get lost in the parenthetical jungle. We write a function that counts how many days have passed from a given date. If you don’t know Lisp, look at the other solutions below first, before coming back to the Lisp one. Look at this mess (and this is as compact as it can be written):

(define (leap-year? year)
  (if (or (not (zero? (remainder year 4)))
          (and (zero? (remainder year 100))
               (not (zero? (remainder year 400)))))
      #f
      #t))

(define (days-since year month day)
  
  (define month-days (list 31 28 31 30 31 30 31 31 30 31 30 31))
  
  (let ((total-days (* (1- year) 365)))
    
    (set! total-days (+ total-days (quotient (1- year) 4)))
    (set! total-days (- total-days (quotient (1- year) 100)))
    (set! total-days (+ total-days (quotient (1- year) 400)))
   
    (do ((i 0 (add1 i)))
        ((= i (1- month)) total-days)
      (set! total-days (+ total-days (list-ref month-days i))))
    
    (if (and (leap-year? year) (> month 2))
        (set! total-days (add1 total-days)))
    (set! total-days (+ total-days day))
    total-days))

That’s horrible. Imagine reading 20000 lines of this. No, thank you, but no means no.

Let’s see the same in the elegant language that I have grown to love. In Swift, it is simply a matter of:

func daysSince(year: Int, month: Int, day: Int) -> Int {
    
    // Array to store days in each month
    let monthDays = [31,28,31,30,31,30,31,31,30,31,30,31]
    
    // Calculate the total number of days in the years before the given year
    var totalDays = (year - 1) * 365
    
    // Add one extra day for each leap year
    totalDays += ((year - 1) / 4) - ((year - 1) / 100) + ((year - 1) / 400)
    
    // Calculate the total number of days in the given year
    for i in 0..<(month-1) {
        totalDays += monthDays[i]
    }
    
    // Add one extra day if it's a leap year and we're past February
    if month > 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
        totalDays += 1
    }
    
    // Add the number of days in the current month
    totalDays += day
    
    return totalDays
}

The annotations make it wordy, but the program logic is visually appealing and clear even without comments (which were added for the benefit of newbies reading this blog). Here is the un-annotated version:

func daysSince(year: Int, month: Int, day: Int) -> Int {
    
    let monthDays = [31,28,31,30,31,30,31,31,30,31,30,31]
    var totalDays = (year - 1) * 365
    
    totalDays += ((year - 1) / 4) - ((year - 1) / 100) + ((year - 1) / 400)
    
    for i in 0..<(month-1) {
        totalDays += monthDays[i]
    }

    if month > 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
        totalDays += 1
    }
    
    totalDays += day
    return totalDays
}

Actually, the clearest programming language is Hope, which is no longer alive, except it is, because Swift seems like modern Hope. Here is the same in Hope:


program DaysSince

function days-since(year: int, month: int, day: int) -> int
    
    var totalDays: int;
    
    let monthDays: vector<int> = [31,28,31,30,31,30,31,31,30,31,30,31];
    
    func is-leap-year(_ year: int) -> bool
        return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    end
    
    totalDays = (year - 1) * 365;
    totalDays += (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400;
    
    for i in 0..<(month - 1)
        totalDays += monthDays[i];
    
    if (is-leap-year(year) && month > 2)
        totalDays += 1;
   
    totalDays += day;
    return totalDays;
end

It’s even clearer than Swift, but as it is a dead language, the second best choice is Swift. This is not to say Swift is the only coolest kid on the block. Let’s look at a few more examples, this one in F#:


let rec daysSince year month day =
    
    let monthDays = [|31; 28; 31; 30; 31; 30; 31; 31; 30; 31; 30; 31|]
    
    let isLeapYear year =
        year % 4 = 0 && (year % 100 <> 0 || year % 400 = 0)
    
    let numberOfLeapYears year =
        year / 4 - year / 100 + year / 400
    
    let mutable totalDays = (year - 1) * 365 + numberOfLeapYears year
    
    for i in 0..month-2 do
        totalDays <- totalDays + monthDays.[i]
    
    if isLeapYear year && month > 2 then
        totalDays <- totalDays + 1
    
    totalDays + day

Pretty neat, although somewhat clanky. Scala also claims to be highly readable:


def daysSince(year: Int, month: Int, day: Int): Int = {
    
    val monthDays = Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
    
    def isLeapYear(year: Int): Boolean =
        year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
    
    val numberOfLeapYears = year / 4 - year / 100 + year / 400
    
    var totalDays = (year - 1) * 365 + numberOfLeapYears
    
    for (i <- 0 until month - 1)
        totalDays += monthDays(i)
    
    if (isLeapYear(year) && month > 2)
        totalDays += 1
    
    totalDays + day
}

That’s good enough for me, although I dislike its for loop.

Anyway, give me Swift and Haskell any day, over any Lisp. Life is too short for parentheses hell.