Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Clojure by

Goal: Create a new flavor of vector that is identical to the built-in vector, except when invoked, uses a different function (a variadic function defined by me, but concat is a good stand-in for demonstration purposes) instead of get.

Invoking a built-in vector implies get.

(def A (vector :a :b :c))
(get A 1) ;; => :b
(A 1) ;; => :b

No matter what we create to accomplish this task, the standard vector should behave exactly as before.

The new flavor of vector would behave ever-so-slightly differently. First, some example definitions using a new function, foo-vector.

(def B (foo-vector :a :b :c)) ;; => [:a :b :c]
(def C (foo-vector 'foo 'bar 'baz)) ;; => ['foo 'bar 'baz]
(def D (foo-vector 42 99)) ;; => [42 99]

All the usual functions work as before, because, for the most part, they are Clojure vectors.

(first B) ;; => :a
(get C 1) ;; => 'bar
(count D) ;; => 2

The only difference would appear when a "foo-vector" is invoked: Instead of an implied get, it's an implied concat (or some other variadic function, but concat works well for discussion).

(B C D) ;; => [:a :b :c 'foo 'bar 'baz 42 99]

Bonus points: Regular vectors and "foo-vectors" containing the same values would satisfy 'equals'.

(= (vector 1 2 3) (foo-vector 1 2 3)) ;; => true

In all respects, "foo-vectors" B, C, and D are vectors, with the exception that when appearing as the first element of a list (i.e., in the function position), an alternative function is called (in our example, concat) instead of get.

Question: What does foo-vector look like to create this new flavor of vector?

I do not know where to begin researching this problem other than regurgitating terms like IFn, deftype, extends, etc. I greatly appreciate any help.

2 Answers

+1 vote
by

I'll preface this by saying that you probably should not do this - changing the semantics of data structures like this is confusing to users of your code/data. The best solution is probably to just make a function that does what you want.

That said, Clojure's core APIs generally do not rely on concrete types but rather core interfaces and you can use deftype to create your own implementation that implements those interfaces (presuming you want to do this in Clojure).

This is an oldie but a goldie helper function for this (https://gist.github.com/semperos/3835392), which you can use to get the scaffolding. Vectors are most importantly implementations of clojure.lang.IPersistentVector:

user=> (scaffold clojure.lang.IPersistentVector)

  clojure.lang.IPersistentCollection
    (equiv [this G__177])
    (count [this])
    (empty [this])
  clojure.lang.Counted
    (count [this])
  clojure.lang.Seqable
    (seq [this])
  clojure.lang.IPersistentVector
    (assocN [this G__178 G__179])
    (length [this])
    (cons [this G__180])
    (cons [this G__181])
  clojure.lang.Indexed
    (nth [this G__182])
    (nth [this G__183 G__184])
  clojure.lang.Associative
    (entryAt [this G__185])
    (assoc [this G__186 G__187])
    (containsKey [this G__188])
  clojure.lang.IPersistentStack
    (pop [this])
    (peek [this])
  clojure.lang.Reversible
    (rseq [this])
  clojure.lang.ILookup
    (valAt [this G__189])
    (valAt [this G__190 G__191])

If you create a deftype with that impl, wrapping a concrete vector and deferring all of those methods to the internal vector, then you have effectively created a custom vector wrapper with your own type.

Missing from the scaffold above is the thing that actually makes concrete vectors a function, namely, clojure.lang.IFn:

user=> 
  clojure.lang.IFn
    (applyTo [this G__499])
    (invoke [this])
    (invoke [this G__685])
    (invoke [this G__683 G__684])
    ... elided  remaining arities...
  java.util.concurrent.Callable
    (call [this])
  java.lang.Runnable
    (run [this])

so you'd want to throw those into your deftype and have all the invoke's do whatever you want. Varargs is supported by applyTo and you can call AFn.applyToHelper().

If this seems like a lot of tedious work, yes it is. Another path to go is to instead subclass PersistentVector directly and implement the invoke methods. You could either do this in Java or in Clojure via genclass.

But to reiterate, you probably shouldn't do this at all.

by
edited by
Thanks for the quick response, Alex. I didn't mention what I'm trying to do because I wasn't sure it was relevant to the question I'm asking.

This is not for a public-facing library. I'm noodling around with a little language whose grammar is expressed most elegantly like `(vec1 vec2 ...)`. In fact, that's nearly exactly what it looks like pencil on paper.

As it is now, I can say `(v-apply vec1 vec2 ...)`, but I am hoping that if I could re-assign the IFn of those particular vector-like-things, while leaving all the others the same, I could write expressions with an implicit `v-apply` instead of the default `get`.

But in constructing, modifying, and moving around `vec1`, et. al., it would be most convenient to have all of the usual core libs available. Furthermore, I'd like to completely preserve the behavior of the clojure.lang.IPersistentVector, so I definitely don't want to mess around with the regular vectors by changing their IFn. I only want to change the invoked-as-a-function behavior of the vector-like-things I write in the mini-language, while inheriting all the other behaviors of clojure.lang.IPersistentVector.
by
Since it seems that `deftype` is bare-bones, I've been attempting to sub-class PersistentVector with `gen-class`.

In a file 'DangerousVector.clj', I have the following:

(ns DangerousVector
  "A vector class that inherits all of Clojure vector behavior, *except* when in
  the function position, concatenates the first vector with the trailing vector."
  (:gen-class
   :name DangerousVector
   :extends clojure.lang.PersistentVector))


(defn -invoke
  ([& vecs]
   (do
     (println "-invoke called")
     (apply concat vecs))))


My intention is to create a new class called 'DangerousVector' that extends clojure.lang.PersistentVector and over-rides the super-class' `invoke` method with a new one that concatenates the vector in the function position with any trailing vectors.

When I compile at a fresh REPL with

blah> (compile 'DangerousVector)

I see the following files generated in directory 'target/classes/dangerous/'

DangerousVector$fn__7418.class
DangerousVector$_invoke.class
DangerousVector$loading__6812__auto____7416.class
DangerousVector__init.class

I am *barely* able to inspect the contents of these files; approximately half the contents are promising-looking strings, with the other half un-renderable characters.

This invocation appears to do something useful

(. DangerousVector (create (cons 1 (cons 2 '(3))))) ;; => [1 2 3]

but when I query its type

(type (. DangerousVector (create '()))) ;; => clojure.lang.PersistentVector

I don't get the expected 'DangerousVector'.

Furthermore, the invoke function doesn't appear to be over-riden.

;; typical `nth` behavior
(let [v (. DangerousVector (create (cons 1 (cons 2 '(3)))))]
  (v 2))
;; => 3


;; not the expected `concat` behavior
(let [v (. DangerousVector (create (cons 1 (cons 2 '(3)))))]
  (v [:a :b :c]))
;; => Unhandled java.lang.IllegalArgumentException
;;    Key must be integer


(Based on this 17-year old chart https://github.com/Chouser/clojure-classes/blob/master/graph-w-legend.svg I focused on clojure.lang.PersistentVector, but I had the same problems trying clojure.lang.LazilyPersistentVector.)


At this point, I've hit a wall, and would appreciate corrections and suggestions. Thanks.
by
To reiterate my original advice, I don't think you should do any of this, it is not a good idea.

If I understand correctly though, your question is why does create return a PersistentVector - and that's because you didn't modify the create methods in your subclass to return a DangerousVector. Subclassing PV probably requires much more extensive overrides.
0 votes
by

Perhaps I should explain a little more about my goals, and you could decide if and how to give me guidance on the best approach.

I would like to implement the S, K, I combinators (and others) as sequential collections of arbitrary values (including nested collections). This works; I can do it with pencil and paper. Lots of paper.

In Clojure, I have composed those combinators using vectors of values. I already have a function appli (intentionally mis-spelled so I don't mask apply) that inspects the contents of the vector that implements the combinator, and that of any arguments, then performs the application of the combinator to the arguments. For example, the Identity combinator, I, is a vector that contains some stuff, such that the Clojure expression

(appli I x)

evaluates to x, corresponding to what I write with pencil on paper, like this

Ix = x

Similarly, the K combinator, K, is a vector that contains some other stuff, such that the Clojure expression

(appli K x y)

yields K's first argument x, corresponding to what I write with pencil on paper

Kxy = x

And for completeness, the S combinator, S, is a vector that contains yet different stuff, such that the Clojure expression

(appli S x y z)

yields ((x z) (y z)), which corresponds to what we might write with pencil on paper

Sxyz = xz(yz)

It would make the ergonomics much better if I could eliminate all those instances of appli sprinkled throughout.

To make the Clojure expressions neatly match the pencil-on-paper versions, I would like for S, K, I, etc., to:

a. When constructed, inspected, and manipulated, behave as a sequential collection of arbitrary values, supporting all the sequence functions of clojure.core;

b. When in the function position of a list, behave as a function with an implicit appli, i.e.,

(I x) ;; => x
(K x y) ;; => x
(S x y z) ;; => ((x z) (y z))

When I originally asked my question, it seemed that a Clojure vector is almost exactly what I wanted: A vector is a sequential of arbitrary values, and may be conveniently created and manipulated by all the Clojure core sequence functions. Plus, when in the function position of a list, a vector behaves as an implicit nth/get function. I want the same behaviors for these combinators, except that when in the function position of a list, instead of nth/get behavior, the combinator should perform appli.

Perhaps the way I presented my question gives the impression that I want to make a wonky vector. I don't actually want to make a data structure, per se, with unusual semantics. What I really want to do is make a function-like thing whose implementation is a sequence of values, which incidentally may be manipulated with the core library (not to mention a Clojure vector's efficiencies from structural sharing and performance guarantees).

But I don't want to make the combinators from everyday functions: The crucial aspect of my study is that it is the contents of the sequence that is the actual implementation of the combinator, and appli, which is a regular everyday function, inspects the contents of the sequence and performs the desired actions.

My first instinct was to make a (hopefully) small modification to Clojure's built-in vector, but you discourage that, and anyways, I am having trouble‡ with the Java aspects. If you have a simpler/performant/cleaner/safer approach, I would certainly try that.

I don't necessarily want to re-implement from the ground up a vector that mis-behaves. I'm just trying to make the Clojure code look like my pencil-on-paper notes.

If I could do something brute force, such as copy-paste clojure/src/jvm/clojure/lang/PersistentVector.java, rename the class, modify the invoke method, then compile, I'd be happy with an ugly solution, as long as it works. But I thought the gen-class approach might somehow be more idiomatic, elegant, and/or correct.

(Would it be better if I submitted this different perspective as a new question?)

‡ Just to illustrate how Java-inept I am: I naively assumed that the create method would be executed in the DangerousVector context, not in the parent class.

by
There are lots of clojure-style implementations of SKI etc out there - have you looked at any of those? Dealing with currying is an important aspect but its one that lots of folks have already tackled in Clojure.

Some examples, I'm sure there are others:

* https://github.com/fogus/skiing/blob/master/src/me/fogus/skiing.clj
* https://github.com/scottbale/combojure/blob/master/src/combojure/ski.clj
...