From: Sam Steingold
Subject: am I the only one who misses these two HT operations?
Date: 
Message-ID: <uzn5291mm.fsf@gnu.org>
I noticed that I often use

(defun pophash (object ht)
  "Remove the value and return it."
  (multiple-value-bind (value present-p) (gethash object ht)
    (when present-p (remhash object ht))
    (values value present-p)))

(defmacro ensure-gethash (object ht default)
  "Just like GETHASH with the default argument,
but DEFAULT is only evaluated when OBJECT is not found and
in that case te value of DEFAULT is placed into (GETHASH OBJECT HT)."
  (with-gensyms ("FINDHASH" obj tab)
    `(let ((,obj ,object) (,tab ,ht))
       (or (gethash ,obj ,tab)
           (setf (gethash ,obj ,tab) ,default)))))

I wonder why they did not make it into the standard...

-- 
Sam Steingold (http://www.podval.org/~sds) running w2k
<http://www.camera.org> <http://www.iris.org.il> <http://www.memri.org/>
<http://www.mideasttruth.com/> <http://www.honestreporting.com>
Any programming language is at its best before it is implemented and used.

From: Kalle Olavi Niemitalo
Subject: Re: am I the only one who misses these two HT operations?
Date: 
Message-ID: <87hdraem9l.fsf@Astalo.kon.iki.fi>
Sam Steingold <···@gnu.org> writes:

> (defmacro ensure-gethash (object ht default)

I could have used this operator, but in the end I didn't bother
to write the macro.

Another nicety might be a MAKE-ARRAY variant that evaluated
the :initial-element argument multiple times.
From: Rahul Jain
Subject: Re: am I the only one who misses these two HT operations?
Date: 
Message-ID: <87ekmez553.fsf@nyct.net>
Kalle Olavi Niemitalo <···@iki.fi> writes:

> Sam Steingold <···@gnu.org> writes:
>
>> (defmacro ensure-gethash (object ht default)
>
> I could have used this operator, but in the end I didn't bother
> to write the macro.

(define-modify-macro identityf identity)

(identityf (gethash object ht default))

which could be optimized a bit by eliding the place-setting on a
successful lookup. However, it allows a well-implemented gethash
setf-expansion to search the hashtable only once. (The place could be a
cache of the location where the lookup should have found the entry.)

> Another nicety might be a MAKE-ARRAY variant that evaluated
> the :initial-element argument multiple times.

(defmacro mapf (seq function)
  (rebinding (seq)
    `(map-into ,seq ,function ,seq)))

Is a nice solution to a more general problem, I think.

-- 
Rahul Jain
·····@nyct.net
Professional Software Developer, Amateur Quantum Mechanicist
From: Peter Seibel
Subject: Re: am I the only one who misses these two HT operations?
Date: 
Message-ID: <m3y8klac4q.fsf@javamonkey.com>
Rahul Jain <·····@nyct.net> writes:

> Kalle Olavi Niemitalo <···@iki.fi> writes:
>
>> Sam Steingold <···@gnu.org> writes:
>>
>>> (defmacro ensure-gethash (object ht default)
>>
>> I could have used this operator, but in the end I didn't bother
>> to write the macro.
>
> (define-modify-macro identityf identity)

Huh? Doesn't define-modify-macro take three arguments? I'm not sure I
understand what you're getting at here. Adding an empty lambda-list
and this generates something that always evaluates the default form.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Kalle Olavi Niemitalo
Subject: Re: am I the only one who misses these two HT operations?
Date: 
Message-ID: <87ekmdsgt2.fsf@Astalo.kon.iki.fi>
Rahul Jain <·····@nyct.net> writes:

> (define-modify-macro identityf identity)
>
> (identityf (gethash object ht default))

That's clever, but evaluates the default even if the key was
already in the hash.

> which could be optimized a bit by eliding the place-setting on a
> successful lookup.

Like this?

  (defmacro ensure-gethash (key hash-table default &environment env)
    (let ((value     (gensym "VALUE"))
          (present-p (gensym "PRESENT-P")))
      (multiple-value-bind (vars vals store-vars writer-form reader-form)
          (get-setf-expansion `(gethash ,key ,hash-table) env)
        `(let* (,@(mapcar #'list vars vals))
           (multiple-value-bind (,value ,present-p) ,reader-form
             (unless ,present-p
               ;; AFAICT, the setf expansion of GETHASH may have multiple
               ;; store variables, though only the value of the first one
               ;; can later be portably retrieved from the hash table.
               (multiple-value-bind (,@store-vars) ,default
                 ,writer-form))
             (values ,value ,present-p))))))

Next, make that setfable.  :-)

> However, it allows a well-implemented gethash setf-expansion to
> search the hashtable only once. (The place could be a cache of
> the location where the lookup should have found the entry.)

I'm not sure that is possible.  Other hash-table operations may
be scheduled between the reader-form and the writer-form, and
they may trigger a rehash.  The cache cannot be invalidated,
because then it would have to be registered somehow, and there is
no hook for unregistering it at the end of the place operation.
For the same reason, one cannot delay the rehash either.

One could however cache e.g. the result of SXHASH, or other
values that do not depend on any modifiable properties of the
hash table.  Do you know of any implementations that do this?

> Kalle Olavi Niemitalo <···@iki.fi> writes:
>> Another nicety might be a MAKE-ARRAY variant that evaluated
>> the :initial-element argument multiple times.
>
> (defmacro mapf (seq function)
>   (rebinding (seq)
>     `(map-into ,seq ,function ,seq)))
>
> Is a nice solution to a more general problem, I think.

Too general.  Compare the verbosity:

  (make-array-foreach 10 :initial-element (make-instance 'foo))

  (mapf (make-array 10 :initial-element nil)
        #'(lambda (ignore)
            (declare (ignore ignore))
            (make-instance 'foo)))

In the latter form, the dummy :initial-element nil is required
because the elements of the array will be read, even though they
are then explicitly ignored.

The form could be shortened to:

  (mapf (make-array 10 :initial-element 'foo)
        #'make-instance)

but then one would probably lose compiler optimizations on
MAKE-INSTANCE with a constant class.
From: Kalle Olavi Niemitalo
Subject: Re: am I the only one who misses these two HT operations?
Date: 
Message-ID: <87k6w55rco.fsf@Astalo.kon.iki.fi>
Kalle Olavi Niemitalo <···@iki.fi> writes:

> The cache cannot be invalidated, because then it would have to
> be registered somehow, and there is no hook for unregistering
> it at the end of the place operation.

On second thought, invalidation does not require registration.
Hash tables could carry a tag that would be changed each time
the table is rehashed.  The tag would then be saved in the
cache, and the reader and writer forms would compare it.

Are the reader and writer forms of a setf expansion allowed to
modify the temporary variables, in order to refresh the cache?
I don't see any restriction on that but it seems a tad unusual
to do.
From: Erling Alf
Subject: Re: am I the only one who misses these two HT operations?
Date: 
Message-ID: <m2657psku7.fsf@x.invalid>
Sam Steingold <···@gnu.org> writes:
> I noticed that I often use
> 
> (defmacro ensure-gethash (object ht default)
>   "Just like GETHASH with the default argument,
> but DEFAULT is only evaluated when OBJECT is not found and
> in that case te value of DEFAULT is placed into (GETHASH OBJECT HT)."
>   (with-gensyms ("FINDHASH" obj tab)
>     `(let ((,obj ,object) (,tab ,ht))
>        (or (gethash ,obj ,tab)
>            (setf (gethash ,obj ,tab) ,default)))))

> I wonder why they did not make it into the standard...

Agreed. I needed a slightly more general macro a week ago, so I made
(slightly renamed to fit your example):

  (ensure (gethash obj ht) default)

(defmacro ensure (place newval &environment env)
  "Essentially (or place (setf place newval))"
  (multiple-value-bind (vars vals putvars putform getform) 
      (get-setf-expansion place env)
    `(let* ,(mapcar #'list vars vals)
       (or ,getform
	   (multiple-value-bind ,putvars
	       ,newval
	     ,putform)))))

There is a slight problem with all this: If the value stored in the
hash is NIL, both our macros will evalute the default expression
again.

For a hash table this could be fixed with

  (m-v-bind (val found) (gethash ...)
     (if found val 
        (setf ....)))

but I can't see a way to make this work generally: For (get) and
(getf), you need to specify a magic value for 'not found', for
(gethash) it is returned as a second value, for (get-properties) it's
the third value, and so on.

Is this really a big mess, or am I missing something trivial?