(map f coll) using memfn

9 views
Skip to first unread message

Timothy Pratley

unread,
Oct 15, 2008, 7:46:41 AM10/15/08
to Clojure
Can I get some help with (map f coll)...

What I want to do is map a java function that takes 2 arguments over a
list
where the first argument is the index into the list itself
and the second argument is taken from the list

The problem being map uses a function of 1 argument.
Does that mean I need to create my own map, or is there a better way?


Specifically what I'm struggling with:

(def *scene-graph* (new javax.media.j3d.BranchGroup))

(defn poly [label points]
(let [la (new javax.media.j3d.LineArray (count points)
(.COORDINATES javax.media.j3d.LineArray)]
(map (memfn javax.media.j3d.LineArray.setCoordinate idx (new
javax.vecmath.Point3f x y 0) points)
(.addChild *scene-graph* (new javax.media.j3d.Shape3D la))))

The intention being that points is a list of x,y coordinates
I construct a LineArray which is the size of points
then for every point I need to call setCoordinate(index, Point3f(x,y))
The LineArray then makes a Shape3D which can be added to the scene
graph.


Regards,
Tim.




Jim Menard

unread,
Oct 15, 2008, 8:21:12 AM10/15/08
to clo...@googlegroups.com
On Wed, Oct 15, 2008 at 7:46 AM, Timothy Pratley
<timothy...@gmail.com> wrote:
>
> Can I get some help with (map f coll)...
>
> What I want to do is map a java function that takes 2 arguments over a
> list
> where the first argument is the index into the list itself
> and the second argument is taken from the list
>
> The problem being map uses a function of 1 argument.
> Does that mean I need to create my own map, or is there a better way?

map can take more than one argument. If it has N arguments, it calls f
with N arguments, each taken from the Nth value of each collection. So
you should be able to pass in an infinite sequence like this UNTESTED
code:

(map f coll (range (count coll)))

Jim

>
>
> Specifically what I'm struggling with:
>
> (def *scene-graph* (new javax.media.j3d.BranchGroup))
>
> (defn poly [label points]
> (let [la (new javax.media.j3d.LineArray (count points)
> (.COORDINATES javax.media.j3d.LineArray)]
> (map (memfn javax.media.j3d.LineArray.setCoordinate idx (new
> javax.vecmath.Point3f x y 0) points)
> (.addChild *scene-graph* (new javax.media.j3d.Shape3D la))))
>
> The intention being that points is a list of x,y coordinates
> I construct a LineArray which is the size of points
> then for every point I need to call setCoordinate(index, Point3f(x,y))
> The LineArray then makes a Shape3D which can be added to the scene
> graph.
>
>
> Regards,
> Tim.
>
>
>
>
>
> >
>

--
Jim Menard, ji...@io.com, jim.m...@gmail.com
http://www.io.com/~jimm/

Jim Menard

unread,
Oct 15, 2008, 11:26:41 AM10/15/08
to clo...@googlegroups.com
On Wed, Oct 15, 2008 at 8:21 AM, I wrote:

> map can take more than one argument. If it has N arguments, it calls f
> with N arguments, each taken from the Nth value of each collection.

Too many "N"s. Restated, map takes a function f and N collections,
each of which should have the same number of elements M. It calls f M
times with N arguments each time, using the i'th element of each of
the collections. (Darn, I'm wordy :-)

Jim

Graham Fawcett

unread,
Oct 15, 2008, 1:09:57 PM10/15/08
to clo...@googlegroups.com
On Wed, Oct 15, 2008 at 8:21 AM, Jim Menard <jim.m...@gmail.com> wrote:
>
> On Wed, Oct 15, 2008 at 7:46 AM, Timothy Pratley
> <timothy...@gmail.com> wrote:
>>
>> Can I get some help with (map f coll)...
>>
>> What I want to do is map a java function that takes 2 arguments over a
>> list
>> where the first argument is the index into the list itself
>> and the second argument is taken from the list
>>
>> The problem being map uses a function of 1 argument.
>> Does that mean I need to create my own map, or is there a better way?
>
> map can take more than one argument. If it has N arguments, it calls f
> with N arguments, each taken from the Nth value of each collection. So
> you should be able to pass in an infinite sequence like this UNTESTED
> code:
>
> (map f coll (range (count coll)))

Rather than (range (count coll)), I would use (iterate inc 0), which
incurs no overhead for counting the argument.

Best,
Graham

Rich Hickey

unread,
Oct 15, 2008, 1:16:48 PM10/15/08
to Clojure


On Oct 15, 11:26 am, "Jim Menard" <jim.men...@gmail.com> wrote:
> On Wed, Oct 15, 2008 at 8:21 AM, I wrote:
> > map can take more than one argument. If it has N arguments, it calls f
> > with N arguments, each taken from the Nth value of each collection.
>
> Too many "N"s. Restated, map takes a function f and N collections,
> each of which should have the same number of elements M. It calls f M
> times with N arguments each time, using the i'th element of each of
> the collections. (Darn, I'm wordy :-)
>

Hmm... I still prefer (doc map), but I'd like to clarify that the
collections do not need to be the same length, map will stop as soon
as any one is exhausted.

Rich

Jim Menard

unread,
Oct 15, 2008, 1:27:52 PM10/15/08
to clo...@googlegroups.com

You're right; that's better. I'm a Clojure newbie, so I appreciate
learning the faster/smaller/more idiomatic way to do things.

Jim

mb

unread,
Oct 15, 2008, 1:44:40 PM10/15/08
to Clojure
Hi,

On 15 Okt., 19:09, "Graham Fawcett" <graham.fawc...@gmail.com> wrote:
> > (map f coll (range (count coll)))
>
> Rather than (range (count coll)), I would use (iterate inc 0), which
> incurs no overhead for counting the argument.

There is not only the overhead of counting, (count coll) might
also destroy the laziness of the collection.

user=> (count (for [i (range 3)] (do (println i) i)))
0
1
2
3

Sincerely
Meikel

Timothy Pratley

unread,
Oct 15, 2008, 11:13:54 PM10/15/08
to Clojure
Thanks so much for all the replies, that showed the way to what I
wanted (simplified example using substring):

(defn lazy-self [me]
((fn rfib [a] (lazy-cons a (rfib a))) me))
(defmacro map-obj [jobj jmeth argbinds & colls]
`(map (memfn ~jmeth ~@argbinds) (lazy-self ~jobj) ~@colls))

(map-obj "Timothy" substring (i j) (iterate inc 0) [3 5 4])

=> ("Tim" "imot" "mo")

ie: I was able to create a macro map-obj which will take a java object
and map a method to a given binding+input collection

Just a few follow on questions...
1) Is there any way to do away with the input bindings altogether? map
doesn't need input bindings, but memfn does. I don't quite grasp why
they are needed for memfn, or how to construct an input binding based
on the number of collections. ie: I would need to generate a list of
symbols the size of the number of collections (map gensym colls) ????
I know that code wont work, but there must be a way.
2) Is there a better way than my creation of a lazy-self function, or
is this pretty much the right thing to do?
3) Would I be better off making a macro that expands into a (doto
statement?


Regards,
Tim.

mb

unread,
Oct 16, 2008, 2:28:27 AM10/16/08
to Clojure
Hi,

On 16 Okt., 05:13, Timothy Pratley <timothyprat...@gmail.com> wrote:
> Just a few follow on questions...
> 1) Is there any way to do away with the input bindings altogether? map
> doesn't need input bindings, but memfn does. I don't quite grasp why
> they are needed for memfn, or how to construct an input binding based
> on the number of collections. ie: I would need to generate a list of
> symbols the size of the number of collections (map gensym colls) ????
> I know that code wont work, but there must be a way.

Maybe you find this helpful: http://paste.lisp.org/display/67182

(map #(jcall "Timothy" 'substring %1 %2) (iterate inc 0) [3 4 5])

Please note, that the annotation does not allow variadic application
while jcall does. It's trivial to cut arguments, eg.

#(jcall obj 'method %1 fixed-arg %2 another-fixed %&)

Sincerely
Meikel

Timothy Pratley

unread,
Oct 16, 2008, 8:31:00 AM10/16/08
to Clojure
Neat tip.
Applying that, this is what I've come up with:

(defn jcall [obj name & args]
(clojure.lang.Reflector/invokeInstanceMethod obj (str name)
(if args (to-array args) clojure.lang.RT/EMPTY_ARRAY)))
(defn map-obj [jobj jmeth coll & colls]
(apply map #(apply jcall jobj jmeth %&) coll colls))

(map-obj "Timothy" 'charAt (range 7))
-> (\T \i \m \o \t \h \y)
(map-obj "Timothy" 'substring (iterate inc 0) [3 5 4])
-> ("Tim" "imot" "mo")
(map-obj "Timothy" 'regionMatches [0 1 0] ["Tim" "Jim" "Jim"] [0 1 0]
[3 2 3])
-> (true true false)

no more (i j) and also no need for the lazy-self seq as the lamda
closes the object, looks like you've killed two of my birds with one
function :)
Thanks.

Rich Hickey

unread,
Oct 16, 2008, 8:41:52 AM10/16/08
to Clojure
I think you've gotten off track by pursuing the use of memfn here when
it isn't a good fit. Presuming your points are 2-element vectors, this
works:

(import '(javax.media.j3d BranchGroup LineArray Shape3D)
'(javax.vecmath Point3f))

(def #^BranchGroup *scene-graph* (BranchGroup.))

(defn poly [label points]
(let [la (LineArray. (count points) LineArray/COORDINATES)]
(dorun
(map (fn [idx [x y]]
(.setCoordinate la (int idx) (Point3f. x y 0)))
(iterate inc 0) points))
(.addChild *scene-graph* (Shape3D. la))))



If you wanted to use memfn, it would have to look like this:

(defn poly [label points]
(let [la (LineArray. (count points) LineArray/COORDINATES)]
(dorun
(map (memfn setCoordinate idx p3)
(repeat la)
(iterate inc 0)
(map (fn [[x y]] (Point3f. x y 0)) points)))
(.addChild *scene-graph* (Shape3D. la))))

Which feels weird, because you are not mapping the memfn across target
'this-es', so you have to repeat the target. Note that in the memfn
case, the call will be use reflection, as there is not a way to convey
the type info through memfn. The other code uses no reflection.

Rich

Timothy Pratley

unread,
Oct 16, 2008, 10:33:42 AM10/16/08
to Clojure
Slam dunk Rich! Yes I can see that is much better.

Thanks for taking the time to understand what I was trying to do
instead of what I thought I was trying to do :)
Reply all
Reply to author
Forward
0 new messages