20230309

The LETV and LETV* Macros

Hello,

It has been a long time since I posted something here.  I have been busy with my day job and bogged down in a major rewrite of something (more on this hopefully very soon now (tm)) that is full of rabbit's holes.

I was able to get out of one of these rabbit's holes with this little hack I cooked up that allows you to, possibly, write more concise code.

This little hack introduces two handy Common Lisp macros, LETV and LETV* that allow you to mix regular LET and MULTIPLE-VALUE-BIND forms in a less verbose way. The amount of indentation needed is also reduced.

The syntax of LETV (or LETV*) is very "loopy", with a nod to SML/OCaml/F#/Haskell/Julia. The current syntax is the following:

    letv     ::= 'LETV' [vars] [IN [body]]
    letvstar ::= 'LETV*' [vars] [IN [body]]

    vars     ::= var | var vars

    var      ::= ids '=' <form> [decls]

    decls    ::= decl | decl decls
    decl     ::= OF-TYPE idtypes

    ids      ::= <symbol> | '(' <symbol> + ')'
    idtypes  ::= <type designator> | '(' <type designator> + ')'

    body     ::= [<declarations>] <form> * 

(I know: the grammar is not completely kosher, but I trust you will understand it.)

The two macros expand in forms that mimic the semantics of let, LET* and MULTIPLE-VALUE-BIND. All type declarations, if present, are properly handled via locally forms. LETV expands into a LET, with variable initialized by PSETQs and multiple-VALUE-SETQs. LETV* expands into a form that is an interleaving of LET* and MULTIPLE-VALUE-BIND.

The library exports only the two symbols LETV and LETV*. The other "symbols" mentioned (=, IN, OF-TYPE) are checked as in LOOP; therefore you can use different styles to write your code, as you would when writing LOOPs.

The library is available here.

Examples

  1. Simple case:
      (letv x = 42 in (format t "The answer is ~D.~%" x))
      
  2. Same with declaraion:
      (letv x = 42 of-type fixnum
    	in (format t "The answer is ~D." x))
      
  3. Simple case with MULTIPLE-VALUE-BIND:
      (letv (v found) = (gethash 'the-key *my-hash-table*)
            in (if found
                   (format t "Found THE-KEY, doing stuff.~%")
                   (error "THE-KEY not found.")))
      
  4. Mixing things up:
      (letv (v found) = (gethash 'the-key *my-hash-table*)
                      of-type (fixnum boolean)
             x = 42
             in (if found
                     (format t "Found THE-KEY, adding to the answer ~D.~%"
                             (+ v x))
                     (error "THE-KEY not found.")))
      
  5. With LETV*:
      (letv* (v found) = (gethash 'the-key *my-hash-table*)
                       of-type (fixnum boolean)
             x = (when found (+ v 42))
             in (if found
                    (format t "Found THE-KEY, adding to the answer ~D.~%"
    			 	        x)
                    (error "THE-KEY not found.")))
      
  6. The other way around.
      (letv x = 42 of-type integer
            (v found) = (gethash 'the-key *my-hash-table*)
                      of-type (fixnum boolean)
            in (if found
                   (format t "Found THE-KEY, adding to the answer ~D.~%"
                           (+ v x))
                   (error "THE-KEY not found.")))
      
  7. A more compelling example.
      (letv* (p found) = (gethash 'the-key *points-table*) of-type (point)
             (x y) = (if found
                         (unpack-point p)
                         (values :missing :missing))
             in
             (declare (type point p)
                      (type real x y))  ; Adding declarations here also works.
    
             (do-stuff-with x y)
             (do-things-with p))
      

All the examples are meant to illustrate the use of LETV and LETV*.

Notes

LETV and LETV* are obviously not the first macros of this kind floating around: others are available and all have their niceties. But I never claimed not to suffer from NIH syndrome.

LETV and LETV* do not do "destructuring" or "pattern matching". That is a different can of worms; but you can check cl-unification for a library (always by yours truly) that provides facilities in that sense.


'(Cheers)