Racketeering — Lisp in the modern world

December 24, 2013 at 11:45 am
filed under Coding
Tagged ,

Recently I decided to give Common Lisp another shot. I like Clojure, but I want to look into Old Stuff like Lisp, and if I can add another scripting language to my toolkit, so much the better.

Except now I’m playing with Racket (Scheme). And, uh, how is it that Racket is this awesome and I hadn’t heard about it sooner? Whereas Common Lisp feels more like the product of its time, Racket feels profoundly modern, while retaining what makes Lisp special. These are of course subjective impressions from a relative Lisp neophyte. That said, I hope I’m not completely off the mark.

The last time around, I stalled out because Common Lisp didn’t seem particularly functional. I didn’t realize at the time that this is by design— Common Lisp is meant to be “powerful.” Of course depending on your perspective this can mean “disorganized,” “confusing,” or “overly complicated.” I can respect the choices but I still find them off-putting.

The LOOP macro versus higher-order functions is an example. LOOP is focused around iteration, and it’s actually a mini-language oriented around same. It leaves me cold. I’m completely sold on higher level constructs like reduce, and I’d rather write sequential operations in those terms than glorified for loops. There’s nothing wrong with for loops. But if I’m going to use FP, why bother with iteration?

Then there’s mapcar, maplist, mapcan, and lord knows what else. There are “destructive” variations on sort and such. These are all options to control how you want something done. Should I spend more time thinking how to do something rather than what to do? I prefer the opposite when programming in a higher level language. It’s why I like how map, reduce, and filter in Clojure all operate over any collection supporting the sequence abstraction.

This is actually something I think Clojure does better even than Racket but maybe I’ll talk about that another time.

Once I finished the toy CL script I’d set out to write (enumerate unique words in a body of text), I decided to check out Racket.

A huge caveat is that I’m still relatively new to all of these languages. My impressions and insight aren’t going to be particularly deep, although I’d hope they’re not completely off the mark.

That said, Racket feels incredibly polished. The website is a case in point. Common Lisp has a number of scattered resources in varying states of updated-ness. The Hyperspec is functional but with that dark gray background, it’s like it’s still 1990 at the latest. Racket has a reference and a guide, both easily searchable, and with hyperlinked references to other Racket constructs.

Threading is one area in particular which drove me a bit crazy. After playing with Go, I think it’s a significant disadvantage for any high level PL not to support CSP idioms. In practice that means channels and green/lightweight threads.

Common Lisp is barely on the map here— AIUI, threads aren’t even in the Common Lisp spec, so by default you’re left with what your implementation provides. Often this ends up looking like OS threads, which is the exact opposite if what I want to be dealing with in a higher level language.

Or you need to use some third party library, which doesn’t exactly give me confidence— there are a few implementations, one of which hasn’t been touched in years. I think most people realize by now that concurrency is hugely important and it ought to be first-class, written & maintained as a core lib.

Racket has channels and threads. Each thread effecitvely comes with its own channel, for crying out loud. There are also other constructs to assist with concurrency, like places and custodians.

If I had to describe what a dead, dying, or fringe language felt like, Common Lisp has a bunch of symptoms I would look out for. My subjective impression is that there’re N libs for a given situation. Half haven’t been touched in years if they’re even available, and the rest cover non-contiguous, sometimes-overlapping use cases.

I don’t mean to bash Common Lisp because I have a great deal of respect for it. I don’t believe that anything old in computer science/engineering is bad, and frankly I may even believe the opposite.

Lisp in particular is timeless in some sense. The particulars — car and cdr and such — often feel old, but affordances like macros, continuations, pattern matching, and tail call optimization are still novel relative to most modern programming languages.

I’m still working on my toy IRC bot implementation. It is slow going in part because I keep ratholing on learning new constructs rather than getting anything done. Yesterday I spent a bunch of time reading about Racket’s macro system, which seems to depart from Lisp’s “conventional” syntax quote, unquote, and so on. It seems like you can use that other stuff, but things like define-syntax-rule make a lot of the really basic stuff substantially easier via pattern matching, etc.

I’ll get there but what with the holidays it’s hard to find a contiguous time to spend time coding, and a new programming language requires somewhat more concentration than one I’m familiar with.

  • Asumu Takikawa

    Nice blog post! Just in case you hadn’t already discovered it, I wanted to note that Racket actually does have generic sequence abstractions.

    > It’s why I like how map, reduce, and filter in Clojure all operate over any collection supporting the sequence abstraction. This is actually something I think Clojure does better even than Racket but maybe I’ll talk about that another time.

    In particular, you can do all of `map`, `reduce`, and `filter` on any sequence in Racket. There are two ways to do this, one more idiomatic than the other.

    The more idiomatic way is to use the `for` macros[1]. You can use them to traverse lists, vectors, dictionaries, and other things. With `#:when` clauses, you get filtering and with `for/fold` you get reduce.

    The other option is to use functions like `sequence-map`, `sequence-filter`, etc. that do the obvious thing.

    [1]: http://www.cs.utah.edu/plt/snapshots/current/doc/guide/for.html

  • Matthew

    Thanks for the comment, and thank you for sharing that! That must be the Racket 6 theme? It’s nice.

    After perusing sequences for a bit, it’s still not quite what I’d meant. Forgive me if you’re already familiar with Clojure, but what I was referring to more specifically was that all (or almost all) collections in Clojure implement the `seq` interface. This means `map`, `filter`, and `reduce` work on hashes, vectors, sets, etc as-is.

    I admit I am slightly biased against `for`. It’s mostly philosophical: `map` and `filter` are slightly more plain about what they’re doing, and with a higher-order function, you can naturally separate out what to do with each element if you want. But of course there are times where `for` is clearer and I’ve used it.

    It seems like sequences and streams are actually pretty close to what I want, providing abstractions over any collection. I thought at first that you’d have to use `for` for sequences, but no, I was wrong and I should’ve read your comment more carefully. There’s {sequence,stream}-fold and the rest. And it looks like you can just invoke those on (e.g.) a hash without an explicit conversion.

    It is definitely a minor point, though. Hopefully I have not come across as too nitpicky! :)

  • Asumu Takikawa

    > That must be the Racket 6 theme? It’s nice.

    Yes, it’s the great new style courtesy of Matthew Butterick[1].

    [1]: http://practicaltypography.com/

    > Forgive me if you’re already familiar with Clojure, but what I was referring to more specifically was that all (or almost all) collections in Clojure implement the `seq` interface. This means `map`, `filter`, and `reduce` work on hashes, vectors, sets, etc as-is.

    As you’ve already found, it’s harder to discover in Racket than in Clojure because the `map`, `fold`, etc. names are unfortunately already taken for the list-specific versions. Maybe this will change in a future Racket version, but it’s part of Racket’s heritage for now.

    > It is definitely a minor point, though. Hopefully I have not come across as too nitpicky! :)

    Oh, definitely not. I just wanted to mention it as an aside. :) Glad to see another blog covering Racket.

  • Matthew

    Yeah, I was a little surprised when I saw `foldl` and `foldr` in lieu of `reduce`, but it wasn’t completely foreign to me; I dabbled in Haskell prior to Clojure and Racket. I still forget from time to time and Ctrl-F for `reduce`, for instance. :)

    Honestly I should just get over myself and use `for` when appropriate. You say it’s more idiomatic than `sequence-foo`, and all else being equal I feel like language idiom should trump my own predilections.

    Also I need to figure out a comment system that allows for markdown formatting and such. :P

%d bloggers like this: