RFC: alist-let

siiky

2022/08/22

2022/08/23

en

(Reply (and an example) at the end)

If you're a Lisper, even if you're not a Schemer, please don't skip this post! :)

History

Almost two years ago I wrote a macro to make it easier to work with values of an alist ("association list" i.e. list of key/value pairs; Scheme's "default" dictionary-like structure). It was called let-aref at first and it could be used to introduce a single variable with the value associated with a key of an alist -- sort of like let but for alists and for a single variable. Soon I realized I could use it for several variables if I changed it only slightly, which became alist-let.

At the time I didn't think much about it, just another tiny macro to make my life easier. But much later (only a few months ago) I needed something like it again. I went searching instead of copying it over and found nothing. Why the heck doesn't something like this exist? The only pattern matcher I know of doesn't seem to support it either.

A bit over a month ago I started thinking of making it more general, because there were lots of assumptions in the original alist-let (most relevant: keys were "simple" symbols, that is, the unquoted key was a valid variable identifier); and also of introducing it to the broader Scheme community.

Now

I first tested the waters on #scheme and some people acknowledged such syntax would be useful, and nobody came up with readily available alternatives. A few days later I sent an RFC to srfi-discuss:

The goal (even though the "project" is for now still called alist-let) is to define some common pattern/syntax for the different dictionary-like types. You can find the latest (new) alist-let in this repo:

If you're a Schemer, what do you think? Send your comments to the list (preferably) or to me directly. If you're a Lisper, even if you're not a Schemer, I would also appreciate your comments. Does your Lisp of choice have something like this? How is it? Let me know!

Reply

Reading back what I wrote I can understand why...

Not very useful, as it is a tautology for the let macro in the first place!

Indeed but that's not it. :p

First question: what is the purpose of alist-let?

It seems you're thinking it's supposed to destructure alists at macro-expansion time? But that's not it, it's for destructuring alists at runtime.

In case you know JavaScript, alist-let is more like the following destructuring syntax:

const obj = { a: 1, b: 2, c: 3 }
const { a, c } = obj
// Do something with a and c
console.log("a=", a, " b=", b)

If you don't know JavaScript, and because I don't like JavaScript and "an example is worth a thousand words" or something: if you're writing a CLI program you're likely to need to parse the command line arguments into (positional) arguments, flags and options. Let's say the program has the flags "--recursive", "--raw-leaves" and "--trickle", and the options "--cid-version" and "--hash".

;(defun main (opts) ...)
(define (main opts)
  (alist-let string=? opts
             ; VAR         KEY           DEFAULT (optional)
             ((recursive   "recursive"   #f)
              (cid-version "cid-version" 1)
              (raw-leaves  "raw-leaves"  ) ; #f is implicitly used as the default
              (trickle     "trickle"     )
              (hash        "hash"        "sha2-256"))
    (print
      "recursive=" recursive #\n     ; recursive=#t
      "cid-version=" cid-version #\n ; cid-version=42
      "raw-leaves=" raw-leaves #\n   ; raw-leaves=#t
      "trickle=" trickle #\n         ; trickle=#f
      "hash=" hash #\n               ; hash="sha2-256"
      )))

; parse-arguments turns something like this: '("--recursive" "--raw-leaves" "--cid-version" "42")
; Into this: '(("recursive" . #t) ("raw-leaves" . #t) ("cid-version" . 42))
(main (parse-arguments (command-line-arguments)))

The only (current) alternative I know of is to manually alist-ref each key (mentioned at the top of the email):

;      VAR                     KEY                         DEFAULT (optional)
(let ((recursive   (alist-ref "recursive"   opts string=?  #t))
      (cid-version (alist-ref "cid-version" opts string=?  1))
      (raw-leaves  (alist-ref "raw-leaves"  opts string=?  #f))
      (trickle     (alist-ref "trickle"     opts string=?  ))
      (hash        (alist-ref "hash"        opts string=?  "sha2-256")))
  ; Do something with recursive, cid-version, raw-leaves, trickle, hash
  (print
    "recursive=" recursive #\n
    "cid-version=" cid-version #\n
    "raw-leaves=" raw-leaves #\n
    "trickle=" trickle #\n
    "hash=" hash #\n))

(alist-ref key alist) is more or less (cdr (assoc key alist))

Hope this helps.

BTW I thought plists were flat alists: (k1 v1 k2 v2 ...) ?