Accessing index inside map

#We have vague plan to make enumerating method to return Enumerator
#when no block is given in the future, so that

···

Yukihiro Matsumoto [mailto:matz@ruby-lang.org] wrote:

#
# require 'enumerator'
# (1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i)
#? x : x*2}
#
#would be
#
# (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}
#
#then. It's much simpler isn't it?

Hi sir Matz,

You're vague plan produces clearer ruby..

but isn't
    each_with_index.map

so close to being
    map_with_index

?

(1..6).map_with_index{|x,i|[2,5].include?(i) ? x : x*2} is very easy to my
tiny brain since all i have to remember is map (just like (in array) all i
have to focus is each, then i follow thru with each_with_index)

i'm sorry, i'm a map/collect fan so pardon my insistence :slight_smile:

thanks in advance.

kind regards
-botp -another spoiled nuby spoiled by map

#
# matz.
#

Hi,

···

In message "Re: accessing index inside map" on Mon, 11 Jul 2005 10:50:31 +0900, "Peña, Botp" <botp@delmonte-phil.com> writes:

# (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}
#
#then. It's much simpler isn't it?

but isn't
   each_with_index.map

so close to being
   map_with_index

?

Yes. But when we add map_with_index, we might be asked to add
collect_with_index, detect_with_index, inject_with_index, and all
other enumerable methods _with_index as well.

              matz.

Hi,

At Mon, 11 Jul 2005 10:50:31 +0900,
Peña, Botp wrote in [ruby-talk:147720]:

You're vague plan produces clearer ruby..

but isn't
    each_with_index.map

so close to being
    map_with_index

?

(1..6).map_with_index{|x,i|[2,5].include?(i) ? x : x*2} is very easy to my
tiny brain since all i have to remember is map (just like (in array) all i
have to focus is each, then i follow thru with each_with_index)

What about Enumerator#with_index?

  [1,2,3,4,5,6].map.with_index {|x,i|[2,5].include?(i) ? x : x*2}

···

--
Nobu Nakada

nobuyoshi nakada wrote:

What about Enumerator#with_index?

  [1,2,3,4,5,6].map.with_index {|x,i|[2,5].include?(i) ? x : x*2}

I like that, personally. In fact each_with_index could become each.with_index (though we obviously need the old one for backwards compatibility.)

Ryan

Hi --

Hi,

># (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}
>#
>#then. It's much simpler isn't it?

>but isn't
> each_with_index.map
>
>so close to being
> map_with_index
>
>?

Yes. But when we add map_with_index, we might be asked to add
collect_with_index, detect_with_index, inject_with_index, and all
other enumerable methods _with_index as well.

I'm probably the person who has asked for map_with_index the most, and
I promise not to ask for *_with_index :slight_smile:

I can understand that a general solution makes sense, though I'm still
very unclear on the whole notion of "index" as it pertains to
Enumerable.

As for:

   (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}

it is simpler than enum_for, but I wonder if it's equally clear. It
seems that one would have to memorize a list of methods that behave
this way, rather than really seeing it directly in the code. Also,
would one have to define, say, #each to return an Enumerator, if one
were writing a new class and mixing in Enumerable?

David

···

On Mon, 11 Jul 2005, Yukihiro Matsumoto wrote:

In message "Re: accessing index inside map" > on Mon, 11 Jul 2005 10:50:31 +0900, "Peña, Botp" <botp@delmonte-phil.com> writes:

--
David A. Black
dblack@wobblini.net

Hi,

At Mon, 11 Jul 2005 14:09:41 +0900,
Ryan Leavengood wrote in [ruby-talk:147729]:

> What about Enumerator#with_index?
>
> [1,2,3,4,5,6].map.with_index {|x,i|[2,5].include?(i) ? x : x*2}

I like that, personally. In fact each_with_index could become
each.with_index (though we obviously need the old one for backwards
compatibility.)

Indeed.

  $ ./ruby -renumerator -e 'p (1..6).to_enum.with_index{|x,i|p [x,i]}'
  [1, 0]
  [2, 1]
  [3, 2]
  [4, 3]
  [5, 4]
  [6, 5]
  1..6

A patch based on the current implementation.

Index: ext/enumerator/enumerator.c

···

===================================================================
RCS file: /cvs/ruby/src/ruby/ext/enumerator/enumerator.c,v
retrieving revision 1.3.2.2
diff -U2 -p -r1.3.2.2 enumerator.c
--- ext/enumerator/enumerator.c 4 Nov 2004 01:20:50 -0000 1.3.2.2
+++ ext/enumerator/enumerator.c 11 Jul 2005 05:25:15 -0000
@@ -162,4 +162,27 @@ enumerator_each(obj)
}

+static VALUE
+enumerator_with_index_i(val, memo)
+ VALUE val, *memo;
+{
+ val = rb_yield_values(2, val, INT2FIX(*memo));
+ ++*memo;
+ return val;
+}
+
+static VALUE
+enumerator_with_index(obj)
+ VALUE obj;
+{
+ VALUE memo = 0;
+
+ obj = (VALUE)rb_node_newnode(NODE_MEMO,
+ rb_ivar_get(obj, id_enum_obj),
+ rb_to_id(rb_ivar_get(obj, id_enum_method)),
+ rb_ivar_get(obj, id_enum_args));
+ return rb_iterate((VALUE (*)_((VALUE)))enumerator_iter, obj,
+ enumerator_with_index_i, (VALUE)&memo);
+}
+
void
Init_enumerator()
@@ -183,4 +206,5 @@ Init_enumerator()
     rb_define_method(rb_cEnumerator, "initialize", enumerator_initialize, -1);
     rb_define_method(rb_cEnumerator, "each", enumerator_each, 0);
+ rb_define_method(rb_cEnumerator, "with_index", enumerator_with_index, 0);

     sym_each = ID2SYM(rb_intern("each"));

--
Nobu Nakada

If we're making this general, I till think 'counter' would be a far better
name than 'index'

martin

···

David A. Black <dblack@wobblini.net> wrote:

I can understand that a general solution makes sense, though I'm still
very unclear on the whole notion of "index" as it pertains to
Enumerable.

I'm joining this converstation late, but ISTM that the fundamental issue
here is that one object may have multiple enumeration methods (e.g. #each
and #each_with_index), but the methods in Enumerable are only able to call
#each. So perhaps a solution is to be able to choose the name of the
enumerator method to call.

e.g.

  module Enumerable
    def altmap(meth=:each)
      res =
      send(meth) { |*args| res << yield(*args) }
      res
    end
  end

  a = [10,20,30,40]
  p a.altmap { |x| x*2 } #=> [20, 40, 60, 80]
  p a.altmap(:each_with_index) { |x,i| x+i } #=> [10, 21, 32, 43]

  h = {1=>"one", 2=>"two", 3=>"three"}
  p h.altmap(:each_key) { |x| x + x } #=> [2, 4, 6]
  p h.altmap(:each_value) { |x| x + x } #=> ["oneone", "twotwo", "threethree"]

Then, Enumerable doesn't have to care about the concept of what's an "index"
- conceptually we move #each_with_index into the object being enumerated,
and it can provide whatever index makes sense to it. (Enumerable could still
provide a default #each_with_index as it does now, though)

Aside: I always found each_with_index to be odd, because we have

   myhash.each { |key,value| ... }
   myarray.each_with_index { |value,key| ... }

I'd like to be able to think of an Array as a special case of Hash, where
the keys just happen to be Integers starting from 0.

This could be regularised if Hash#each_with_index did { |k,v| yield v,k }

Regards,

Brian.

···

On Mon, Jul 11, 2005 at 08:07:37PM +0900, David A. Black wrote:

I'm probably the person who has asked for map_with_index the most, and
I promise not to ask for *_with_index :slight_smile:

I can understand that a general solution makes sense, though I'm still
very unclear on the whole notion of "index" as it pertains to
Enumerable.

Hi,

···

In message "Re: accessing index inside map" on Mon, 11 Jul 2005 14:29:24 +0900, nobuyoshi nakada <nobuyoshi.nakada@ge.com> writes:

$ ./ruby -renumerator -e 'p (1..6).to_enum.with_index{|x,i|p [x,i]}'
[1, 0]
[2, 1]
[3, 2]
[4, 3]
[5, 4]
[6, 5]
1..6

A patch based on the current implementation.

Can you commit this patch to the HEAD?

              matz.

P.S. I note that some Enumerable methods already take arguments:
detect/find, grep, inject could have meth=:each as a later argument for
backwards-compatibility.

The only awkward ones are to_set and zip. Even then, they could be made
backwards-compatible by checking if the first argument is a Symbol and using
it as a method name in that case.

I don't really see the usefulness of the foo.to_set(klass,*args) syntax
anyway, given that it just calls klass.new(foo,*args). However, I can see
uses for
    h.to_set(:each_key)
    h.to_set(:each_value)

Regards,

Brian.

···

On Tue, Jul 12, 2005 at 10:04:02AM +0100, Brian Candler wrote:

  module Enumerable
    def altmap(meth=:each)
      res =
      send(meth) { |*args| res << yield(*args) }
      res
    end
  end

Hi,

At Mon, 11 Jul 2005 14:57:00 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:147731]:

> $ ./ruby -renumerator -e 'p (1..6).to_enum.with_index{|x,i|p [x,i]}'
> [1, 0]
> [2, 1]
> [3, 2]
> [4, 3]
> [5, 4]
> [6, 5]
> 1..6
>
>A patch based on the current implementation.

Can you commit this patch to the HEAD?

For performance, I'd like to make Enumerator a subclass of
Data instead of Object, in the HEAD.

Index: ext/enumerator/enumerator.c

···

===================================================================
RCS file: /cvs/ruby/src/ruby/ext/enumerator/enumerator.c,v
retrieving revision 1.5
diff -U2 -p -r1.5 enumerator.c
--- ext/enumerator/enumerator.c 2 Nov 2004 07:38:21 -0000 1.5
+++ ext/enumerator/enumerator.c 11 Jul 2005 07:38:27 -0000
@@ -18,5 +18,77 @@
static VALUE rb_cEnumerator;
static ID sym_each, sym_each_with_index, sym_each_slice, sym_each_cons;
-static ID id_new, id_enum_obj, id_enum_method, id_enum_args;
+#if !defined(HAVE_RB_PROC_CALL) || !defined(HAVE_RB_METHOD_CALL)
+static ID id_call;
+#endif
+
+static VALUE
+proc_call(proc, args)
+ VALUE proc, args;
+{
+#ifdef HAVE_RB_PROC_CALL
+ if (TYPE(args) != T_ARRAY) {
+ args = rb_values_new(1, args);
+ }
+ return rb_proc_call(proc, args);
+#else
+ return rb_funcall2(proc, id_call, 1, &args);
+#endif
+}
+
+static VALUE
+method_call(method, args)
+ VALUE method, args;
+{
+#ifdef HAVE_RB_METHOD_CALL
+ return rb_method_call(RARRAY(args)->len, RARRAY(args)->ptr, method);
+#else
+ return rb_funcall2(method, id_call, RARRAY(args)->len, RARRAY(args)->ptr);
+#endif
+}
+
+struct enumerator {
+ VALUE method;
+ VALUE proc;
+ VALUE args;
+ VALUE (*iter)_((VALUE, struct enumerator *));
+};
+
+static void enumerator_mark _((void *));
+static void
+enumerator_mark(p)
+ void *p;
+{
+ struct enumerator *ptr = p;
+ rb_gc_mark(ptr->method);
+ rb_gc_mark(ptr->proc);
+ rb_gc_mark(ptr->args);
+}
+
+static struct enumerator *
+enumerator_ptr(obj)
+ VALUE obj;
+{
+ struct enumerator *ptr;
+
+ Data_Get_Struct(obj, struct enumerator, ptr);
+ if (RDATA(obj)->dmark != enumerator_mark) {
+ rb_raise(rb_eTypeError,
+ "wrong argument type %s (expected Enumerable::Enumerator)",
+ rb_obj_classname(obj));
+ }
+ if (!ptr) {
+ rb_raise(rb_eArgError, "uninitialized enumerator");
+ }
+ return ptr;
+}
+
+static VALUE enumerator_iter_i _((VALUE, struct enumerator *));
+static VALUE
+enumerator_iter_i(i, e)
+ VALUE i;
+ struct enumerator *e;
+{
+ return rb_yield(proc_call(e->proc, i));
+}

static VALUE
@@ -26,5 +98,7 @@ obj_to_enum(obj, enum_args)
     rb_ary_unshift(enum_args, obj);

- return rb_apply(rb_cEnumerator, id_new, enum_args);
+ return rb_class_new_instance(RARRAY(enum_args)->len,
+ RARRAY(enum_args)->ptr,
+ rb_cEnumerator);
}

@@ -33,5 +107,8 @@ enumerator_enum_with_index(obj)
     VALUE obj;
{
- return rb_funcall(rb_cEnumerator, id_new, 2, obj, sym_each_with_index);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_with_index;
+ return rb_class_new_instance(2, args, rb_cEnumerator);
}

@@ -39,8 +116,8 @@ static VALUE
each_slice_i(val, memo)
     VALUE val;
- NODE *memo;
+ VALUE *memo;
{
- VALUE ary = memo->u1.value;
- long size = memo->u3.cnt;
+ VALUE ary = memo[0];
+ long size = (long)memo[1];

     rb_ary_push(ary, val);
@@ -48,5 +125,5 @@ each_slice_i(val, memo)
     if (RARRAY(ary)->len == size) {
   rb_yield(ary);
- memo->u1.value = rb_ary_new2(size);
+ memo[0] = rb_ary_new2(size);
     }

@@ -59,14 +136,14 @@ enum_each_slice(obj, n)
{
     long size = NUM2LONG(n);
- NODE *memo;
- VALUE ary;
+ VALUE args[2], ary;

     if (size <= 0) rb_raise(rb_eArgError, "invalid slice size");

- memo = rb_node_newnode(NODE_MEMO, rb_ary_new2(size), 0, size);
+ args[0] = rb_ary_new2(size);
+ args[1] = (VALUE)size;

- rb_iterate(rb_each, obj, each_slice_i, (VALUE)memo);
+ rb_iterate(rb_each, obj, each_slice_i, (VALUE)args);

- ary = memo->u1.value;
+ ary = args[0];
     if (RARRAY(ary)->len > 0) rb_yield(ary);

@@ -78,5 +155,9 @@ enumerator_enum_slice(obj, n)
     VALUE obj, n;
{
- return rb_funcall(rb_cEnumerator, id_new, 3, obj, sym_each_slice, n);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_slice;
+ args[2] = n;
+ return rb_class_new_instance(3, args, rb_cEnumerator);
}

@@ -84,8 +165,8 @@ static VALUE
each_cons_i(val, memo)
     VALUE val;
- NODE *memo;
+ VALUE *memo;
{
- VALUE ary = memo->u1.value;
- long size = memo->u3.cnt;
+ VALUE ary = memo[0];
+ long size = (long)memo[1];

     if (RARRAY(ary)->len == size) {
@@ -104,10 +185,11 @@ enum_each_cons(obj, n)
{
     long size = NUM2LONG(n);
- NODE *memo;
+ VALUE args[2];

     if (size <= 0) rb_raise(rb_eArgError, "invalid size");
- memo = rb_node_newnode(NODE_MEMO, rb_ary_new2(size), 0, size);
+ args[0] = rb_ary_new2(size);
+ args[1] = (VALUE)size;

- rb_iterate(rb_each, obj, each_cons_i, (VALUE)memo);
+ rb_iterate(rb_each, obj, each_cons_i, (VALUE)args);

     return Qnil;
@@ -118,5 +200,19 @@ enumerator_enum_cons(obj, n)
     VALUE obj, n;
{
- return rb_funcall(rb_cEnumerator, id_new, 3, obj, sym_each_cons, n);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_cons;
+ args[2] = n;
+ return rb_class_new_instance(3, args, rb_cEnumerator);
+}
+
+static VALUE enumerator_allocate _((VALUE));
+static VALUE
+enumerator_allocate(klass)
+ VALUE klass;
+{
+ struct enumerator *ptr;
+ return Data_Make_Struct(rb_cEnumerator, struct enumerator,
+ enumerator_mark, -1, ptr);
}

@@ -128,4 +224,5 @@ enumerator_initialize(argc, argv, obj)
{
     VALUE enum_obj, enum_method, enum_args;
+ struct enumerator *ptr = enumerator_ptr(obj);

     rb_scan_args(argc, argv, "11*", &enum_obj, &enum_method, &enum_args);
@@ -134,16 +231,25 @@ enumerator_initialize(argc, argv, obj)
   enum_method = sym_each;

- rb_ivar_set(obj, id_enum_obj, enum_obj);
- rb_ivar_set(obj, id_enum_method, enum_method);
- rb_ivar_set(obj, id_enum_args, enum_args);
+ ptr->method = rb_obj_method(enum_obj, enum_method);
+ if (rb_block_given_p()) {
+ ptr->proc = rb_block_proc();
+ ptr->iter = enumerator_iter_i;
+ }
+ else {
+ ptr->iter = (VALUE (*) _((VALUE, struct enumerator *)))rb_yield;
+ }
+ ptr->args = enum_args;

- return Qnil;
+ return obj;
}

+static VALUE enumerator_iter _((VALUE));
static VALUE
enumerator_iter(memo)
- NODE *memo;
+ VALUE memo;
{
- return rb_apply(memo->u1.value, memo->u2.id, memo->u3.value);
+ struct enumerator *e = (struct enumerator *)memo;
+
+ return method_call(e->method, e->args);
}

@@ -152,14 +258,29 @@ enumerator_each(obj)
     VALUE obj;
{
- VALUE val;
+ struct enumerator *e = enumerator_ptr(obj);

- obj = (VALUE)rb_node_newnode(NODE_MEMO,
- rb_ivar_get(obj, id_enum_obj),
- rb_to_id(rb_ivar_get(obj, id_enum_method)),
- rb_ivar_get(obj, id_enum_args));
- val = rb_iterate((VALUE (*)_((VALUE)))enumerator_iter, obj, rb_yield, 0);
+ return rb_iterate(enumerator_iter, (VALUE)e, e->iter, (VALUE)e);
+}
+
+static VALUE
+enumerator_with_index_i(val, memo)
+ VALUE val, *memo;
+{
+ val = rb_yield_values(2, val, INT2FIX(*memo));
+ ++*memo;
     return val;
}

+static VALUE
+enumerator_with_index(obj)
+ VALUE obj;
+{
+ struct enumerator *e = enumerator_ptr(obj);
+ VALUE memo = 0;
+
+ return rb_iterate(enumerator_iter, (VALUE)e,
+ enumerator_with_index_i, (VALUE)&memo);
+}
+
void
Init_enumerator()
@@ -181,6 +302,8 @@ Init_enumerator()
     rb_include_module(rb_cEnumerator, rb_mEnumerable);

+ rb_define_alloc_func(rb_cEnumerator, enumerator_allocate);
     rb_define_method(rb_cEnumerator, "initialize", enumerator_initialize, -1);
     rb_define_method(rb_cEnumerator, "each", enumerator_each, 0);
+ rb_define_method(rb_cEnumerator, "with_index", enumerator_with_index, 0);

     sym_each = ID2SYM(rb_intern("each"));
@@ -189,7 +312,6 @@ Init_enumerator()
     sym_each_cons = ID2SYM(rb_intern("each_cons"));

- id_new = rb_intern("new");
- id_enum_obj = rb_intern("enum_obj");
- id_enum_method = rb_intern("enum_method");
- id_enum_args = rb_intern("enum_args");
+#if !defined(HAVE_RB_PROC_CALL) || !defined(HAVE_RB_METHOD_CALL)
+ id_call = rb_intern("call");
+#endif
}
Index: ext/enumerator/extconf.rb

RCS file: /cvs/ruby/src/ruby/ext/enumerator/extconf.rb,v
retrieving revision 1.1
diff -U2 -p -r1.1 extconf.rb
--- ext/enumerator/extconf.rb 1 Nov 2004 05:04:04 -0000 1.1
+++ ext/enumerator/extconf.rb 23 Jun 2005 02:55:54 -0000
@@ -1,2 +1,5 @@
require 'mkmf'
+
+%w"rb_obj_method rb_method_call".all? {|f| have_func(f, "ruby.h")}
+have_func("rb_proc_call", "ruby.h")
create_makefile('enumerator')

--
Nobu Nakada

Brian Candler wrote:

  module Enumerable
    def altmap(meth=:each)
      res =
      send(meth) { |*args| res << yield(*args) }
      res
    end
  end

P.S. I note that some Enumerable methods already take arguments:
detect/find, grep, inject could have meth=:each as a later argument
for backwards-compatibility.

The only awkward ones are to_set and zip. Even then, they could be
made backwards-compatible by checking if the first argument is a
Symbol and using it as a method name in that case.

I don't really see the usefulness of the foo.to_set(klass,*args)
syntax anyway, given that it just calls klass.new(foo,*args).
However, I can see uses for
    h.to_set(:each_key)
    h.to_set(:each_value)

IMHO the enumerator approach is slightly superior as your suggestion can
lead to conflicts with current method arguments as you have noticed.
Personally I don't have a problem using

a.to_enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

I think this is more flexible and modularized. And the overhead of a new
instance creation is usually neglectible compared to the iteration
overhead.

Kind regards

    robert

···

On Tue, Jul 12, 2005 at 10:04:02AM +0100, Brian Candler wrote:

IMHO the enumerator approach is slightly superior as your suggestion can
lead to conflicts with current method arguments as you have noticed.
Personally I don't have a problem using

a.to_enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

I think this is more flexible and modularized. And the overhead of a new
instance creation is usually neglectible compared to the iteration
overhead.

"Do the simplest thing which will possibly work" :slight_smile:

Actually I think my version reads better:

  a.inject(0,:each_with_index) {|sum,(a,i)| sum + a*i}

"Starting with 0, call :each_with_index with the following block".

Regards,

Brian.

Hi,

···

In message "Re: accessing index inside map" on Mon, 11 Jul 2005 16:41:45 +0900, nobuyoshi nakada <nobuyoshi.nakada@ge.com> writes:

Can you commit this patch to the HEAD?

For performance, I'd like to make Enumerator a subclass of
Data instead of Object, in the HEAD.

It's all up to you. Although I feel no need to check rb_proc_call,
since enumerator is bundled with Ruby itself.

              matz.

Brian Candler wrote:

IMHO the enumerator approach is slightly superior as your suggestion
can lead to conflicts with current method arguments as you have
noticed. Personally I don't have a problem using

a.to_enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

I think this is more flexible and modularized. And the overhead of
a new instance creation is usually neglectible compared to the
iteration overhead.

"Do the simplest thing which will possibly work" :slight_smile:

Well, apparently we have differing opinions on this. Personally I find
the Enumerator approach simpler as it does not clutter the original's
class interface (introducing an optional parameter on each method doubles
the number of legal invocations). :slight_smile:

Actually I think my version reads better:

  a.inject(0,:each_with_index) {|sum,(a,i)| sum + a*i}

"Starting with 0, call :each_with_index with the following block".

There is a chance that folks who don't know #inject might read "start with
index 0".

Well, I think a vote is in order. Did you consider submitting this as
RCR?

Kind regards

    robert

Well, apparently we have differing opinions on this. Personally I find
the Enumerator approach simpler as it does not clutter the original's
class interface (introducing an optional parameter on each method doubles
the number of legal invocations). :slight_smile:

My own interpretation of "do the simplest" includes "don't introduce any
unnecessary abstractions". Arguably, the 'optional' parameter is always
there; it just happens to default to :each. There are lots of similar cases
in the Ruby standard library.

The Enumerator stuff is new to me, since I'm still just using 1.8.2.
However, I find obj.to_enum(:each_with_index) confusing; after all, obj is
*already* enumerable. Making this more general, you might get

  obj.rename(:each=>:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

Creating an object just to say "call #each_with_index instead of #each"
seems wasteful unless you plan to re-use it, given that you could just call
#each_with_index in the first place.

However, perhaps 'to_enum' could be called something friendlier, e.g.

  obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

In fact, even

  obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

reads better to me, as it's not stressing the creation of an intermediate
object. to_foo looks like you are converting obj into something completely
different, rather than just adding a temporary wrapper.

From the OP's question, then, it's a question of

  [1,2,3,4,5,6].map(:each_with_index) { |x,i| [2,5].include?(i) ? x : x*2 }

versus

  [1,2,3,4,5,6].enum(:each_with_index).map { |x,i| [2,5].include?(i) ? x : x*2 }

I still prefer the first :slight_smile: It's a mapping operation, applied directly to
an Array.

Well, I think a vote is in order. Did you consider submitting this as
RCR?

I'm not familiar with the RCR process.

I just thought I'd wait and see what the list members (and Matz in
particular) thought. This idea seems sufficiently obvious that I guess
there's some reason why it has not been implemented already.

Regards,

Brian.

Hi,

At Mon, 11 Jul 2005 16:48:29 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:147737]:

>> Can you commit this patch to the HEAD?
>
>For performance, I'd like to make Enumerator a subclass of
>Data instead of Object, in the HEAD.

It's all up to you. Although I feel no need to check rb_proc_call,
since enumerator is bundled with Ruby itself.

Static function proc_call() is defined now but rb_proc_call()
is not. Also rb_obj_method() and rb_method_call().

···

--
Nobu Nakada

Hi --

Well, apparently we have differing opinions on this. Personally I find
the Enumerator approach simpler as it does not clutter the original's
class interface (introducing an optional parameter on each method doubles
the number of legal invocations). :slight_smile:

My own interpretation of "do the simplest" includes "don't introduce any
unnecessary abstractions". Arguably, the 'optional' parameter is always
there; it just happens to default to :each. There are lots of similar cases
in the Ruby standard library.

The Enumerator stuff is new to me, since I'm still just using 1.8.2.
However, I find obj.to_enum(:each_with_index) confusing; after all, obj is
*already* enumerable. Making this more general, you might get

obj.rename(:each=>:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

Creating an object just to say "call #each_with_index instead of #each"
seems wasteful unless you plan to re-use it, given that you could just call
#each_with_index in the first place.

However, perhaps 'to_enum' could be called something friendlier, e.g.

obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

In fact, even

obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

reads better to me, as it's not stressing the creation of an intermediate
object. to_foo looks like you are converting obj into something completely
different, rather than just adding a temporary wrapper.

I wonder whether one might (re)introduce the "iter[ator]" set of
terms, rather than enumerator. I'm not totally clear on the relation
between them, so I may be off the mark, but I was thinking of things
like:

   obj.iterate_with(:each_with_index).inject ...

a little bit on the model of sort_by. Then again, this may be getting
dangerously close to the rejected RCR at
RCRS.

I have to admit I haven't totally assimilated the Enumerator
mechanisms, which strike me as very useful and powerful but sort of a
secondary system overlaid on the whole iterator and/or enumerable
thing. I guess the barrier, for me, is having the separate Enumerator
object, rather than just having it happen in the course of calling a
method. I guess that's also what's powerful about it :slight_smile:

I'm not familiar with the RCR process.

See http://www.rcrchive.net.

David

···

On Wed, 13 Jul 2005, Brian Candler wrote:

--
David A. Black
dblack@wobblini.net

Hi,

At Wed, 13 Jul 2005 01:10:19 +0900,
Brian Candler wrote in [ruby-talk:147876]:

However, perhaps 'to_enum' could be called something friendlier, e.g.

  obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

`using' sounds too general.

In fact, even

  obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

reads better to me, as it's not stressing the creation of an intermediate
object. to_foo looks like you are converting obj into something completely
different, rather than just adding a temporary wrapper.

Enumerable#to_enum has an alias named as #enum_for.

···

--
Nobu Nakada

Brian Candler wrote:

Well, apparently we have differing opinions on this. Personally I
find the Enumerator approach simpler as it does not clutter the
original's class interface (introducing an optional parameter on
each method doubles the number of legal invocations). :slight_smile:

My own interpretation of "do the simplest" includes "don't introduce
any unnecessary abstractions". Arguably, the 'optional' parameter is
always
there; it just happens to default to :each. There are lots of similar
cases
in the Ruby standard library.

The Enumerator stuff is new to me, since I'm still just using 1.8.2.

It's in 1.8.2 and I think it's been around for some time, maybe even as
far back as 1.7.*.

However, I find obj.to_enum(:each_with_index) confusing; after all,
obj is *already* enumerable.

Well, to_enum is rather a shorthand for to_enumerator. There's also
enum_for (as Nobu pointed out).

Making this more general, you might get

  obj.rename(:each=>:each_with_index).inject(0) {|sum,(a,i)| sum +
a*i}

With this naming I'd rather associate that :each is renamed on the
instance obj to :each_with_index and that this change is permanent. The
to_xyz notation clearly indicates a conversion i.e. you expect to get a
new isntance.

Creating an object just to say "call #each_with_index instead of
#each"
seems wasteful unless you plan to re-use it, given that you could
just call #each_with_index in the first place.

It's not that uncommon in OO oriented languages to create temporary
objects. For example command pattern has often this property: you create
an instance, set all properties you need for processing, execute it, fetch
the result and drop the instance again. This is a nice way of doing
housekeeping for temporary state that is solely associated with the
calculation.

The overhead in this case is neglectible in the general case, i.e., an
iteration that creates lots of new objects along the way.

However, perhaps 'to_enum' could be called something friendlier, e.g.

  obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

using for what?

In fact, even

  obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

That reads better IMHO.

reads better to me, as it's not stressing the creation of an
intermediate object. to_foo looks like you are converting obj into
something completely different, rather than just adding a temporary
wrapper.

Yeah, maybe. Anyway, this is far better than rename IMHO.

From the OP's question, then, it's a question of

  [1,2,3,4,5,6].map(:each_with_index) { |x,i| [2,5].include?(i) ? x :
x*2 }

versus

  [1,2,3,4,5,6].enum(:each_with_index).map { |x,i| [2,5].include?(i)
? x : x*2 }

I still prefer the first :slight_smile: It's a mapping operation, applied
directly to
an Array.

Well, I think a vote is in order. Did you consider submitting this
as RCR?

I'm not familiar with the RCR process.

It's fairly easy: you register, create your RCR (there are some helpful
questions on the website) and submit it. Then you wait for comments.

See http://www.rcrchive.net.

I just thought I'd wait and see what the list members (and Matz in
particular) thought. This idea seems sufficiently obvious that I guess
there's some reason why it has not been implemented already.

Certainly also a good approach. The main advantage I see with RCR's is
that there is a reference point and all the comments are tied together.

Kind regards

    robert