From: Will McCutchen
Subject: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <1118783061.951371.37320@g49g2000cwa.googlegroups.com>
In the program I am writing (actually, porting from Python) to help me
learn Lisp, I find myself using a construct like this fairly
frequently:

(let ((rubrik (gethash 'rubrik all-fields))
      (section (gethash 'section all-fields))
      (number (gethash 'number all-fields)))
  (format t "~A-~A-~A" rubrik section number))

This, to my Lisp-virgin eyes, looks like a good opportunity for a
macro.  I would like to be able to write something like this

(destructure-hashtable (rubrik section number) table
  (format t "~A-~A-~A" rubrik section number))

and have it expand out to the let statement above (or something
comparable).  My macro-fu skills aren't good enough to produce this at
all.  I honestly can't quite wrap my mind around how it should be done.

Is this feasible?  Can anyone out there give me any pointers?

Thanks,


Will.

From: Nicolas Neuss
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <871x74mxws.fsf@ortler.iwr.uni-heidelberg.de>
Somethind like this?

(defmacro with-entries (entries table &body body)
  (let ((obj (gensyms)))
     `(let ((,obj ,table))
        (symbol-macrolet ,(loop for entry in entries collect
                             `(,entry (gethash ',entry ,obj)))
     ,@body))))
     
(with-entries (a b c) (make-hash-table)
   (setf a 1) (setf b 2) (print a) (print b))

Look also at CLOS objects and WITH-SLOTS.

Nicolas.
From: Andreas Thiele
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <d8nip9$8v2$02$1@news.t-online.com>
"Will McCutchen" <·········@gmail.com> schrieb im Newsbeitrag
····························@g49g2000cwa.googlegroups.com...
> In the program I am writing (actually, porting from Python) to help me
> learn Lisp, I find myself using a construct like this fairly
> frequently:
>
> (let ((rubrik (gethash 'rubrik all-fields))
>       (section (gethash 'section all-fields))
>       (number (gethash 'number all-fields)))
>   (format t "~A-~A-~A" rubrik section number))
>
> This, to my Lisp-virgin eyes, looks like a good opportunity for a
> macro.  I would like to be able to write something like this
>
> (destructure-hashtable (rubrik section number) table
>   (format t "~A-~A-~A" rubrik section number))
>
> and have it expand out to the let statement above (or something
> comparable).  My macro-fu skills aren't good enough to produce this at
> all.  I honestly can't quite wrap my mind around how it should be done.
>
> Is this feasible?  Can anyone out there give me any pointers?
>
> Thanks,
>
>
> Will.
>

I wrote:

(defmacro dh ((&rest fields) table &body body)
  `(let (,@(mapcar (lambda (field) (list field (list 'gethash (list 'quote
field) table))) fields))
     ,@body))

where

(pprint (macroexpand '(dh (a b c) tb (blubb a))))

results to

(LET ((A (GETHASH 'A TB)) (B (GETHASH 'B TB)) (C (GETHASH 'C TB))) (BLUBB
A))

in LispWorks.

Andreas
From: Sashank Varma
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <1118814582.207991.209970@z14g2000cwz.googlegroups.com>
Andreas Thiele wrote:

> (defmacro dh ((&rest fields) table &body body)
>   `(let (,@(mapcar (lambda (field) (list field (list 'gethash (list 'quote
> field) table))) fields))
>      ,@body))

Nice. You can clean it up a bit more if you nest backquotes.

(defmacro dh2 ((&rest fields) table &body body)
  `(let (,@(mapcar (lambda (field)
                     `(,field (gethash ',field ,table)))
                   fields))
     ,@body))

Sashank
From: Andreas Thiele
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <d8ookq$8o8$00$1@news.t-online.com>
"Sashank Varma" <············@yahoo.com> schrieb im Newsbeitrag
·····························@z14g2000cwz.googlegroups.com...
> Andreas Thiele wrote:
>
> > (defmacro dh ((&rest fields) table &body body)
> >   `(let (,@(mapcar (lambda (field) (list field (list 'gethash (list
'quote
> > field) table))) fields))
> >      ,@body))
>
> Nice. You can clean it up a bit more if you nest backquotes.
>
> (defmacro dh2 ((&rest fields) table &body body)
>   `(let (,@(mapcar (lambda (field)
>                      `(,field (gethash ',field ,table)))
>                    fields))
>      ,@body))
>
> Sashank
>

I wanted to leave something as exercise to the reader :)

Andreas
From: Sashank Varma
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <1118899868.378352.103180@f14g2000cwb.googlegroups.com>
Andreas Thiele wrote:
> > Nice. You can clean it up a bit more if you nest backquotes.
> >
> > (defmacro dh2 ((&rest fields) table &body body)
> >   `(let (,@(mapcar (lambda (field)
> >                      `(,field (gethash ',field ,table)))
> >                    fields))
> >      ,@body))
> 
> I wanted to leave something as exercise to the reader :)

D'oh!
From: Pascal Bourguignon
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <87psuoty4d.fsf@thalassa.informatimago.com>
"Will McCutchen" <·········@gmail.com> writes:

> In the program I am writing (actually, porting from Python) to help me
> learn Lisp, I find myself using a construct like this fairly
> frequently:
>
> (let ((rubrik (gethash 'rubrik all-fields))
>       (section (gethash 'section all-fields))
>       (number (gethash 'number all-fields)))
>   (format t "~A-~A-~A" rubrik section number))
>
> This, to my Lisp-virgin eyes, looks like a good opportunity for a
> macro.  I would like to be able to write something like this
>
> (destructure-hashtable (rubrik section number) table
>   (format t "~A-~A-~A" rubrik section number))
>
> and have it expand out to the let statement above (or something
> comparable).  My macro-fu skills aren't good enough to produce this at
> all.  I honestly can't quite wrap my mind around how it should be done.

What about your function-fu?

> Is this feasible?  Can anyone out there give me any pointers?

I find the name destructure-hashtable ill-indicated. There's no
destructuring at hand, you just fetch a set of values.

What about a function that would be called as:

     (gen-multiple-gethash '(rubrik section number) 'hashtable '...)

and would return a s-expression like:

     (let* ((T1 hashtable)
            (rubrik  (gethash 'rubrik  T1))
            (section (gethash 'section T1))
            (number  (gethash 'number  T1)))
         ...)

We need to bind the hashtable to avoid multiple evaluation of an
expression, for example with: (gen-multiple-gethash '(a) '(aref hashes
(incf i)) '(print a))


Is your function-fu good enough to generate such a list?  


;; rot13 to protect the innocents:
(qrsha tra-zhygvcyr-trgunfu (xrlf gnoyr &erfg obql)
  (yrg ((ignoyr (traflz)))
   `(yrg* ((,ignoyr ,gnoyr)
            ,@(zncpne (ynzoqn (xrl) `(,xrl (trgunfu ',xrl ,ignoyr))) xrlf))
        ,@obql)))


[165]> (gen-multiple-gethash '(rubrick section number)
                              '(aref hashes (incf i))
                  '(print rubrick) '(print section) '(print number))
(LET*
 ((#:G3806 (AREF HASHES (INCF I))) (RUBRICK (GETHASH 'RUBRICK #:G3806))
  (SECTION (GETHASH 'SECTION #:G3806)) (NUMBER (GETHASH 'NUMBER #:G3806)))
 (PRINT RUBRICK) (PRINT SECTION) (PRINT NUMBER))
[166]> 


So now that you've got some function-fu, just add a thin layer of macro-fu:

(defmacro with-multiple-gethash (keys table &body body)
   (apply (function gen-multiple-gethash) keys table body))

[169]> (macroexpand '(with-multiple-gethash (rubrick section number) (aref hashes (incf i))
                              (print rubrick) (print section) (print number)))
(LET*
 ((#:G3808 (AREF HASHES (INCF I))) (RUBRICK (GETHASH 'RUBRICK #:G3808))
  (SECTION (GETHASH 'SECTION #:G3808)) (NUMBER (GETHASH 'NUMBER #:G3808)))
 (PRINT RUBRICK) (PRINT SECTION) (PRINT NUMBER)) ;
T


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
Wanna go outside.
Oh, no! Help! I got outside!
Let me back inside!
From: Kalle Olavi Niemitalo
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <87zmtsshhg.fsf@Astalo.kon.iki.fi>
"Will McCutchen" <·········@gmail.com> writes:

> I would like to be able to write something like this
>
> (destructure-hashtable (rubrik section number) table
>   (format t "~A-~A-~A" rubrik section number))

(defmacro destructure-hashtable ((&rest vars) table-form &body body)
  (let ((table-var (gensym)))
    ;; These two LETs could be written as one LET*, but see below.
    `(let ((,table-var ,table-form))
       (let ,(loop for var in vars
                   collect `(,var (gethash ',var ,table-var)))
         ,@body))))

You can even replace the inner LET with a SYMBOL-MACROLET so that
the hash table can be modified via the fake variables.

However, this seems like an unusual use for hash tables.
I'd probably use structure-objects or standard-objects instead.
See also the WITH-SLOTS macro.

Also, destructuring normally supports recursion: an object that
was found by destructuring a data structure can itself be further
destructured with a nested destructuring pattern.  Because this
macro does not support anything like that, it should be called
something else.  I considered WITH-HASH-KEYS and WITH-HASH-VALUES
(see CLHS Section 6.1.2.1.6, for-as-hash) but couldn't decide
which one (if either) would be right.
From: Thomas A. Russ
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <ymik6kvtq8m.fsf@sevak.isi.edu>
Kalle Olavi Niemitalo <···@iki.fi> writes:

> (defmacro destructure-hashtable ((&rest vars) table-form &body body)
...

> However, this seems like an unusual use for hash tables.
> I'd probably use structure-objects or standard-objects instead.
> See also the WITH-SLOTS macro.

Perhaps a more reasonable name of this macro would be WITH-HASH-VALUES.

-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Tayssir John Gabbour
Subject: Re: Something like DESTRUCTURING-BIND for hashtables
Date: 
Message-ID: <1118870734.028806.183670@g14g2000cwa.googlegroups.com>
Kalle Olavi Niemitalo wrote:
> "Will McCutchen" <·········@gmail.com> writes:
> > I would like to be able to write something like this
> >
> > (destructure-hashtable (rubrik section number) table
> >   (format t "~A-~A-~A" rubrik section number))
>
> (defmacro destructure-hashtable ((&rest vars) table-form &body body)
>   (let ((table-var (gensym)))
>     ;; These two LETs could be written as one LET*, but see below.
>     `(let ((,table-var ,table-form))
>        (let ,(loop for var in vars
>                    collect `(,var (gethash ',var ,table-var)))
>          ,@body))))
>
> You can even replace the inner LET with a SYMBOL-MACROLET so that
> the hash table can be modified via the fake variables.
>
> However, this seems like an unusual use for hash tables.
> I'd probably use structure-objects or standard-objects instead.
> See also the WITH-SLOTS macro.

I think something like this would be useful:

  (with-hash-values ((k1 "key1") (k2 "key2") (k3 :key3))
      table
    (setf k2 "new hash-value for k2")
    (setf k3 "new hash-value for k3")
    ...)

So you can have fairly arbitrary hash-keys. And a shortcut for the
original poster can be
(with-hash-values k1 ...)
instead of
(with-hash-values (k1 'k1) ...)
though I don't know whether it's really useful in the long run.

Essentially Nicolas's code:

(defmacro with-hash-values (entries table &body body)
  "Like WITH-SLOTS, but different."
  (let ((=table= (gensym "table-")))
    `(let ((,=table= ,table))
      (symbol-macrolet
            ,(loop for entry in entries collect
                   (cond ((and (listp entry)
                               (= (length entry) 2)
                               (symbolp (first entry)))
                          (destructuring-bind (varname hash-key)
                              entry
                            `(,varname (gethash ,hash-key ,=table=))))
                         ((symbolp entry)
                          `(,entry (gethash ',entry ,=table=)))
                         (t
                          (error "In WITH-HASH-VALUES, each member of
ENTRIES should either be a variable-name or list of two objects with
the first being a variable name: ~S" entry))))
          ,@body))))



Tayssir