From: TJ Atkins
Subject: Creating setf expanders on methods
Date: 
Message-ID: <2aecda39-9b48-434c-ab2e-49c2ea9ed9d4@h20g2000yqn.googlegroups.com>
Yesterday I realized that the various accessor methods for different
data types (elt, aref, gethash, etc.) are a bit of an annoyance.
Especially gethash, with its non-standard argument order.  So, I put
together a ref method that specializes on types and calls the right
method.  Simple stuff.

Obviously, though, I want ref to act appropriately in setf.  After a
good bit of hunting, I found that defmethod accepts (setf --name--)
just like defun.  This worked great for sequences and hashes, but I've
run into some trouble for arrays.

Code so far:
(defgeneric ref (data index)
    (:documentation "Generic reference resolver."))

(defmethod ref ((data sequence) index)
    (elt data index))
(defmethod ref ((data hash-table) index)
    (gethash index data))
(defmethod ref ((data array) indexes)
    (apply 'aref (cons data indexes)))

(defmethod (setf ref) (value (data sequence) index)
    (setf (elt data index) value))
(defmethod (setf ref) (value (data hash-table) index)
    (setf (gethash index data) value))

But continuing the trivial transformation for the array-specialized
(setf ref) doesn't work, because obviously setf doesn't know how to do
a transformation on (apply 'aref (cons data indexes)).  However, I
don't know any other way to grab a potentially unbounded number of
array indexes and apply them.

I could figure out how to put together a full setf-expander for aref,
but I don't think that would help me in the case of a defmethod.  So
how would I do this?  Thanks for the education.  ^_^

From: Joshua Taylor
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <c6ad1315-2746-460c-9951-c6a1a8f254e6@l22g2000vba.googlegroups.com>
On Feb 25, 8:40 am, TJ Atkins <··········@gmail.com> wrote:
> Yesterday I realized that the various accessor methods for different
> data types (elt, aref, gethash, etc.) are a bit of an annoyance.
> Especially gethash, with its non-standard argument order.  So, I put
> together a ref method that specializes on types and calls the right
> method.  Simple stuff.
>
> Obviously, though, I want ref to act appropriately in setf.  After a
> good bit of hunting, I found that defmethod accepts (setf --name--)
> just like defun.  This worked great for sequences and hashes, but I've
> run into some trouble for arrays.
>
> Code so far:
> (defgeneric ref (data index)
>     (:documentation "Generic reference resolver."))
>
> (defmethod ref ((data sequence) index)
>     (elt data index))
> (defmethod ref ((data hash-table) index)
>     (gethash index data))
> (defmethod ref ((data array) indexes)
>     (apply 'aref (cons data indexes)))
>
> (defmethod (setf ref) (value (data sequence) index)
>     (setf (elt data index) value))
> (defmethod (setf ref) (value (data hash-table) index)
>     (setf (gethash index data) value))
>
> But continuing the trivial transformation for the array-specialized
> (setf ref) doesn't work, because obviously setf doesn't know how to do
> a transformation on (apply 'aref (cons data indexes)).  However, I
> don't know any other way to grab a potentially unbounded number of
> array indexes and apply them.
>
> I could figure out how to put together a full setf-expander for aref,
> but I don't think that would help me in the case of a defmethod.  So
> how would I do this?  Thanks for the education.  ^_^

You're expecting too little from the language, you can setf to an
apply #'aref:


CL-USER > (defvar *array* (make-array '(2 2 2)))
*ARRAY*

CL-USER > *array*
#3A(((NIL NIL) (NIL NIL)) ((NIL NIL) (NIL NIL)))

CL-USER 4 > (setf (apply #'aref *array* '(1 1 1)) t)
T

CL-USER 5 > *array*
#3A(((NIL NIL) (NIL NIL)) ((NIL NIL) (NIL T)))
..........................................^...


And for the ref methods:


CL-USER > (defmethod ref ((data array) indices)
              (apply #'aref data indices))
#<STANDARD-METHOD REF NIL (ARRAY T) 21C1002F>

CL-USER > (defmethod (setf ref) (value (data array) indices)
              (setf (apply #'aref data indices) value))
#<STANDARD-METHOD (SETF REF) NIL (T ARRAY T) 21D1EBDB>

CL-USER > (ref *array* '(0 0 0))
NIL

CL-USER > (setf (ref *array* '(0 0 0)) :new-value)
:NEW-VALUE

CL-USER > *array*
#3A(((:NEW-VALUE NIL) (NIL NIL)) ((NIL NIL) (NIL T)))
......^^^^^^^^^^.....................................

Two things to note though:
1) Only the last argument to apply needs to be a list.  So no need to
(apply 'aref (cons data indices)) --- (apply 'aref data indices) works
as well.
2) The values of F where (setf (apply f ...) ...) works are limited.
But you seem to be digging into the HyperSpec, so I'm sure you can
find the special cases.  An example of the behavior:

CL-USER > (setf (apply 'aref *array* '(0 1 0)) 78)

Error: SETF of APPLY needs a #'ed function name, got (QUOTE AREF).
  1 (abort) Return to level 0.
  2 Return to top loop level 0.

Type :b for backtrace, :c <option number> to proceed,  or :? for other
options

Cheers,
//JT
From: Marco Antoniotti
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <dca4f9b9-d801-4029-a1c3-80a732d3a34f@z9g2000yqi.googlegroups.com>
On Feb 25, 2:40 pm, TJ Atkins <··········@gmail.com> wrote:
> Yesterday I realized that the various accessor methods for different
> data types (elt, aref, gethash, etc.) are a bit of an annoyance.
> Especially gethash, with its non-standard argument order.  So, I put
> together a ref method that specializes on types and calls the right
> method.  Simple stuff.

Yep.  Been there, done that.  The *really* annoying thing though is
the immensely useful default argument to GETHASH.


> Obviously, though, I want ref to act appropriately in setf.  After a
> good bit of hunting, I found that defmethod accepts (setf --name--)
> just like defun.  This worked great for sequences and hashes, but I've
> run into some trouble for arrays.
>
> Code so far:
> (defgeneric ref (data index)
>     (:documentation "Generic reference resolver."))
>
> (defmethod ref ((data sequence) index)
>     (elt data index))
> (defmethod ref ((data hash-table) index)
>     (gethash index data))
> (defmethod ref ((data array) indexes)
>     (apply 'aref (cons data indexes)))
>
> (defmethod (setf ref) (value (data sequence) index)
>     (setf (elt data index) value))
> (defmethod (setf ref) (value (data hash-table) index)
>     (setf (gethash index data) value))
>
> But continuing the trivial transformation for the array-specialized
> (setf ref) doesn't work, because obviously setf doesn't know how to do
> a transformation on (apply 'aref (cons data indexes)).  However, I
> don't know any other way to grab a potentially unbounded number of
> array indexes and apply them.
>
> I could figure out how to put together a full setf-expander for aref,
> but I don't think that would help me in the case of a defmethod.  So
> how would I do this?  Thanks for the education.  ^_^

I think it id best to change the arguments to REF

(defmethod ref ((a array) &rest indices)
  (apply #'aref a indices)) ; No extra consing.

(defmethod (setf ref) (v (a array) &rest indices)
  (setf (apply #'aref a indices) v))

Cheers
--
Marco
From: TJ Atkins
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <d2aaf73f-ed04-4f09-8384-cc8bd2a0a7c1@c36g2000yqn.googlegroups.com>
@Joshua Taylor:

Thanks for the notes.  Don't know why I never learned that bit about
apply.

I also have no idea why my setf for aref wasn't working before.  I put
it together exactly as you have now, but it threw an error.  It's
possible it was due to environment crud from previous testing.

Interestingly, CCL doesn't require #'ing the function name.  I can
just pass it 'aref and it's fine.  Suppose I should sharp-quote anyway
for compat, though.

@Pascal Bourguignon:
Thank you for the section reference!

@Marco Antoniotti:
Ah, thank you for reminding me about the default argument.  Handled
now.

I originally used a &rest param, but when it looked like arrays might
not be doable, I dropped it.  It's back in now, though.


Thanks, all.  It seems like this error could have been avoided had I
just reloaded my lisp image and tested one more time, but I wasn't
sure enough of my correctness to think that doing so would solve
anything.  Thank you all for your input.

~TJ
From: Rob Warnock
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <qfWdnTaEqLdlbjjUnZ2dnUVZ_vCdnZ2d@speakeasy.net>
TJ Atkins  <··········@gmail.com> wrote:
+---------------
| @Marco Antoniotti:
| Ah, thank you for reminding me about the default argument.
| Handled now.
+---------------

Just curious... How did you "handle it"?

Like Marco, I consider the default argument to GETHASH incredibly useful,
especially when counting things, e.g., (incf (gethash key ht 0)).


-Rob

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Pillsy
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <3376c7c6-9783-4257-97c1-c675607b5cb8@r28g2000vbp.googlegroups.com>
On Feb 25, 8:44 pm, ····@rpw3.org (Rob Warnock) wrote:
[...]
> Like Marco, I consider the default argument to GETHASH incredibly useful,
> especially when counting things, e.g., (incf (gethash key ht 0)).

I still remember how pleased I was when I discovered that

(push 'quux (gethash 'baz *table*))

did exactly what I wanted it to.

Cheers,
Pillsy
From: TJ Atkins
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <bca59c50-72f5-4618-98f6-5f3804cc77ce@a39g2000yqc.googlegroups.com>
On Feb 25, 7:44 pm, ····@rpw3.org (Rob Warnock) wrote:
> TJ Atkins  <··········@gmail.com> wrote:
> +---------------
> | @Marco Antoniotti:
> | Ah, thank you for reminding me about the default argument.
> | Handled now.
> +---------------
>
> Just curious... How did you "handle it"?
>
> Like Marco, I consider the default argument to GETHASH incredibly useful,
> especially when counting things, e.g., (incf (gethash key ht 0)).

ref went back to having a (place &rest indexes) argument signature, so
in the case of a hash table, (first indexes) is the key and (second
indexes) is the default value (matching gethash's argument signature,
except with the place in front like it should be).  Conveniently,
(second indexes) returns nil on a 1-element list, which is precisely
the standard default value for hashes.

~TJ
From: Marco Antoniotti
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <7a6fce1d-10db-45a9-837e-9b4352d8cf24@l39g2000yqn.googlegroups.com>
On Feb 27, 3:25 am, TJ Atkins <··········@gmail.com> wrote:
> On Feb 25, 7:44 pm, ····@rpw3.org (Rob Warnock) wrote:
>
> > TJ Atkins  <··········@gmail.com> wrote:
> > +---------------
> > | @Marco Antoniotti:
> > | Ah, thank you for reminding me about the default argument.
> > | Handled now.
> > +---------------
>
> > Just curious... How did you "handle it"?
>
> > Like Marco, I consider the default argument to GETHASH incredibly useful,
> > especially when counting things, e.g., (incf (gethash key ht 0)).
>
> ref went back to having a (place &rest indexes) argument signature, so
> in the case of a hash table, (first indexes) is the key and (second
> indexes) is the default value (matching gethash's argument signature,
> except with the place in front like it should be).  Conveniently,
> (second indexes) returns nil on a 1-element list, which is precisely
> the standard default value for hashes.

Yep.  That'd work.  I would also change the signature to

(defgeneric ref (container index/key &rest more-indexes-or-defaults))
;; We LOVE long indentifiers :)

After all you always have at least one index/key.  I think this is
better from the programmer point of view, although it may be worse for
the CLOS discriminating engine. YMMV, do profile, etc etc.

Cheers
--
Marco
From: Rob Warnock
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <bPydneGPeqgjEjfUnZ2dnUVZ_jOWnZ2d@speakeasy.net>
Marco Antoniotti <·······@gmail.com> wrote:
+---------------
| TJ Atkins <··········@gmail.com> wrote:
| > ····@rpw3.org (Rob Warnock) wrote:
| > > Just curious... How did you "handle it"?
| > > Like Marco, I consider the default argument to GETHASH incredibly useful,
| > > especially when counting things, e.g., (incf (gethash key ht 0)).
| >
| > ref went back to having a (place &rest indexes) argument signature, so
| > in the case of a hash table, (first indexes) is the key and (second
| > indexes) is the default value (matching gethash's argument signature,
| > except with the place in front like it should be). �Conveniently,
| > (second indexes) returns nil on a 1-element list, which is precisely
| > the standard default value for hashes.
| 
| Yep.  That'd work.  I would also change the signature to
| 
| (defgeneric ref (container index/key &rest more-indexes-or-defaults))
| ;; We LOVE long indentifiers :)
| 
| After all you always have at least one index/key.  I think this is
| better from the programmer point of view...
+---------------

Heh. Looking for a place to file this, I stumbled across a 2002 thread
about the very same topic [a REF macro and/or special form], in which
you had this to say [slightly reformatted for brevity]:

    Newsgroups: comp.lang.lisp
    Date: 23 Jan 2002 10:19:58 -0500
    Subject: REF as a special operator (Re: Ciel (was: Re: The true faith))
    From: Marco Antoniotti <·······@cs.nyu.edu>
    Message-ID: <··················@octagon.mrl.nyu.edu>
    ...
    (defmacro ref (item (&rest indexes-or-keys)
			&key default
			&allow-other-keys) ...)

    So that for an 1D array A1: (ref a1 3) == (aref a1 3)
    for a 2D array A2:          (ref a2 (3 4)) == (aref a2 3 4)
    for an hash-table HT:       (ref ht 'key) == (gethash 'key ht)
    again:                      (ref ht 'key :default 4) == (gethash 'key ht 4)
    ...[some discussion of VALUES]...
    for an instance I of class C, [with
    slot (s :accessor s-of)]:   (ref I 's) == (slot-value I 's)
    but then for a hairy data structure HDT
    indexed by strings....      (ref hdt ("string1" "s2" "s3") :default 123)

To which Thomas Burdick added:

    I just posted something similar, and made a dismissive comment about
    it, but I think I like this take on it.  There's one dereferencing
    position, and it can be either a subscript or a list of subscripts.
    I think I'd want the position to be evaluated [so your second example
    would be (ref a2 '(3 4)) ].
    ...

    > (ref I 's) == (slot-value I 's

    I actually don't like this as a default behavior.  As a report from
    the field, I've already caught one bug because an error was raised by
    trying to REF an object that wasn't REF'able.  If all classes were
    REF'able by default, this would have bitten me later.  It's pretty
    easy to make a ref-mixin class. ...

Does this blast from the past inform/change your current suggestion(s)?


-Rob

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Pascal J. Bourguignon
Subject: Re: Creating setf expanders on methods
Date: 
Message-ID: <7ctz6iwpgw.fsf@pbourguignon.anevia.com>
TJ Atkins <··········@gmail.com> writes:
> I could figure out how to put together a full setf-expander for aref,
> but I don't think that would help me in the case of a defmethod.  So
> how would I do this?  Thanks for the education.  ^_^

(defmethod (setf ref) (new-value (self array) indices)
  (assert (not (vectorp array)))
  (setf (apply (function aref) self indices) new-value))

(let ((array (make-array '(2 2) :initial-element 1))) (setf (apply (function aref) array '(0 1)) 0) array)
--> #2A((1 0) (1 1))

See "5.1.2.5 APPLY Forms as Places"

-- 
__Pascal Bourguignon__