Modify vector so it can be invoked with two arguments

I'm playing with a matrix implementation in Clojure which I'm doing for the fun of doing it and learning more about Clojure, rather than because I want to create the bestest fastest most coolest matrix implementation in the world.

One of the primary operations needed in code like this is the ability to return the value at a given row and column in a matrix, which of course I've written as a function

(mat-getrc m 2 3)

says "Give me the value at row 2, column 3 in matrix m". Perfectly good Clojure, but verbose and ugly. I'd rather write

(m 2 3)

but of course A) vectors (in my package matrices are just vectors) only respond to a single argument, and B) vectors don't know how to use the row and column number to figure out where the correct value is stored.

From looking at the docs for IFn (which vectors are supposed to implement) it appears that a two-argument version of invoke exists - but how do I get my "matrix" vectors to implement and respond to it?

Any suggestions and pointing-in-the-right-direction appreciated.

Upvotes: 3

Views: 137

Answers (2)

Alex Miller
Alex Miller

Reputation: 70239

You can't modify how vectors are invoked as that's built into the implementation of vector, but you can define your own type that wraps a vector, acts as a vector, and is invokable however you like with deftype. You would need to extend many of the same interfaces that vectors implement (this is however a large list):

user=> (ancestors clojure.lang.PersistentVector)
#{clojure.lang.IEditableCollection clojure.lang.ILookup 
  java.util.concurrent.Callable java.lang.Runnable clojure.lang.IMeta 
  java.lang.Comparable clojure.lang.IReduceInit
  clojure.lang.IPersistentCollection clojure.lang.IHashEq java.lang.Iterable 
  clojure.lang.IReduce java.util.List clojure.lang.AFn clojure.lang.Indexed 
  clojure.lang.Sequential clojure.lang.IPersistentStack java.io.Serializable 
  clojure.lang.Reversible clojure.lang.Counted java.util.Collection 
  java.util.RandomAccess java.lang.Object clojure.lang.Seqable 
  clojure.lang.Associative clojure.lang.APersistentVector 
  clojure.lang.IKVReduce clojure.lang.IPersistentVector clojure.lang.IObj 
  clojure.lang.IFn}

Upvotes: 2

Chris Murphy
Chris Murphy

Reputation: 6509

(def matrix [[1 2 3 4][5 6 7 8][9 10 11 12]])

As you say in your question this is possible:

(matrix 2)

But this is not:

(matrix 2 3)

This would be a standard way to get the index of an index:

(get-in matrix [2 3])

You can already nearly get what you want, just with a few more parens:

((matrix 2) 3)

You could define a higher order function:

(defn matrix-hof [matrix]
  (fn [x y]
    (get-in matrix [x y])))

Then put the function rather than the matrix in function position:

(let [m (matrix-hof matrix)]
    (m 2 3))

I don't believe that exactly what you are asking is possible using either a function or a macro.

Upvotes: 2

Related Questions