juanmirocks
juanmirocks

Reputation: 6189

Access array with list of indices

I want to access arrays with the indices being in a list. Let's call this utility arefl, and it's defined as follows:

(arefl array '(x y z ...)) equals (aref array x y z ...) 

My goal is to create some generic functions that operate on matrices of any size.

I have failed to implement such a thing with macros and just aref. The closest thing that I have is:

(defmacro arefl (array is)
  "Access array by a list of indices"
  `(apply #'aref (cons ,array ,is)))

which works, and actually it also works with (setf (arefl array is) value) but the compiler, at least sbcl, throws a warning telling me that I'm redefining setf for (I guess) apply. The warning is:

; in: DEFUN (SETF AREFL**)
;     (SETF (APPLY #'AREF ARRAY (REDUCE 'CONS ARGS :FROM-END T)) NEW-VALUE)
; --> LET* MULTIPLE-VALUE-BIND LET APPLY MULTIPLE-VALUE-CALL 
; ==>
;   #'(SETF AREF)
; 
; caught STYLE-WARNING:
;   defining as a SETF function a name that already has a SETF macro:
;     (SETF AREF)
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition

--

Is there a better way? I'm looking for an implementation that works well with setf and does not need a call to another function like apply nor does do any cons

Upvotes: 1

Views: 134

Answers (2)

Joshua Taylor
Joshua Taylor

Reputation: 85883

First, though I recognize that you said

I'm looking for an implementation that works well with setf and does not need a call to another function like apply nor does do any cons

however, you can simply use apply 'aref here, and you don't need to do any consing, since only apply's final argument needs to be a list. That means that all the following are equivalent:

(aref array 0 1)
(apply 'aref (list array 0 1))
(apply 'aref array (list 0 1))
(apply 'aref array 0 (list 1))
(apply 'aref array 0 1 '())

Most importantly, if you want to avoid calling cons, it means that you can do

(apply 'aref array indices)

You can use setf with this too (although you will have to use #'array, and not 'array):

(setf (apply #'aref array indices) new-value)

Since apply works here, you just need to make your aref* and (setf aref*) functions (to be analogous with list*):

(defun aref* (array &rest args)
  (apply 'aref array (reduce 'cons args :from-end t)))
             
(defun (setf aref*) (new-value array &rest args)
  (setf (apply #'aref array (reduce 'cons args :from-end t)) new-value))

The (reduce 'cons args :from-end t) in those is used to support spreadable argument list designators, which are what apply uses. Using this idiom, you can pass exactly the same kinds of arguments to (aref* ...) that you could use in (apply #'aref ...). That might be a bit more complex than the use cases that you've described, but it means that rather than having to specifically describe what sorts of arguments aref* takes, you can simply say (like the documentation for apply does), that aref*'s args are a spreadable argument list designator, and that aref* applies aref to the args.

Upvotes: 2

Lars Brinkhoff
Lars Brinkhoff

Reputation: 14325

Ok, define-setf-expander is overkill for this.

(defun arefl (array list)
  (apply #'aref array list))
(defun (setf arefl) (x array list)
  (setf (apply #'aref array list) x))

See "APPLY Forms as Places": http://clhs.lisp.se/Body/05_abe.htm

Upvotes: 2

Related Questions