ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* |rcr|.xv  Index Variables ( *_with_index )
@ 2003-09-02 14:48 daz
  2003-09-02 15:11 ` Gavin Sinclair
  2003-09-02 17:01 ` why the lucky stiff
  0 siblings, 2 replies; 11+ messages in thread
From: daz @ 2003-09-02 14:48 UTC (permalink / raw
  To: ruby-core (d)

[-- Attachment #1: Type: text/plain, Size: 10070 bytes --]


Hello 'core,

My first post here, so I'll try not to be too controversial.
It might be too long for busy people.

=begin

  [RCR] proposal - optional block index variable
  ==============================================

* Applies to all iterators, including future additions.

* No new methods.  "with_index" depleted.

* Problem of naming (map_with_indexes/indices) disappears.

* No code breakage.

  "A Language shouldn't get in your way"
* Removes an area where IMO it does.

=end


=begin

Quoting David Black from ruby-talk.org/57500,

  If we had map_with_indices, you could do:
    myarray.map_with_indices {|e,i| e + ", " if i % 2 == 0}.compact

  but we don't.  (Hint, hint.)

End quote.

  myarray.map {|e|.i  e + ", " if i % 2 == 0}.compact
=end


=begin

Private letter to the President of Citizens for MWI, Inc.

  Dear Dr. Black,

  I think the time has arrived for the final push.
  We know where he lives.  We have his photograph ;-/
  All we need now is camping equipment and hot soup.

               Let's violate PoLS !

  Yours faithfully,

  A. Citizen.

=end



=================================================================
Ruby examples:           ruby 1.8.0 (2003-08-30+) [i586-bccwin32]
=================================================================

----------8<-----------------------------------------------------

a = ('a'..'z').to_a
a.map! {|x|.n  ' ' + x + (n+1).to_s if n % 2 == 0}
a.display

#->  a1 c3 e5 g7 i9 k11 m13 o15 q17 s19 u21 w23 y25

----------8<----------------------------------------->8----------

loop {.x
  x < 5 or break
  p x
}

----------8<----------------------------------------->8----------

=begin
 "each_byte_with_index" (etc.) doesn't exist.
=end

'Hello'.each_byte {|ch|.ix puts ch}

#-> 72
#-> 101
#-> 108
#-> 108
#-> 111

----------8<----------------------------------------->8----------

#
#  Until each_with_index gets flushed
#  down the toilet, we could do ...
#
#    each_with_index (with index)           Mmm, sounds nice.

(10..16).each_with_index do |n, wx|.index
  p [n, wx, index]
  raise 'Oh-Oh!' if index != wx
end

#-> [10, 0, 0]
#-> [11, 1, 1]
#-> [12, 2, 2]
#-> [13, 3, 3]
#-> [14, 4, 4]
#-> [15, 5, 5]
#-> [16, 6, 6]

----------8<----------------------------------------->8----------

10.times {|n|.n  n+1}

#-> C:/TEMP/rbA115.TMP:  2: duplicate name for block parameter / index variable
#-> 10.times {|n|.n  n+1}
#->                ^

----------8<----------------------------------------->8----------

y = 10
flg = 'a'
2.times do | z2 |.x
  3.times do | z3 |.y
    puts "  #{z2}#{x}, #{z3}#{y}, (#{flg})"
    if [z2, x, z3, y, flg] == [1,1,1,1,'a']
      flg = 'c'
      puts '* state change *'
      retry  #  retry/redo
    end
    puts '-'*15
  end
end
puts "final y = #{y}"

#->   00, 00, (a)
#-> ---------------
#->   00, 11, (a)
#-> ---------------
#->   00, 22, (a)
#-> ---------------
#->   11, 00, (a)
#-> ---------------
#->   11, 11, (a)
#-> * state change *
#->   11, 00, (c)
#-> ---------------
#->   11, 11, (c)
#-> ---------------
#->   11, 22, (c)
#-> ---------------
#-> final y = 2


=begin
From: http://zem.novylen.net/ruby/withindex.rb

module Enumerable
  def method_missing(meth, *args, &block)
    if meth.to_s =~ /_with_index/
      m = meth.to_s.gsub!('_with_index','')
      i = -1
      self.send(m,*args) {|n|
        i = i+1
        block.call(n,i)
      }
    end
  end
end

=end

=begin
From: http://www.loveruby.net/~aamine/ja/tdiary/20021210.html
#      (extract of translation, edited)

unless [ ].respond_to?(:Sort_by)
  class Array
    def sort_by
      list = [ ]
      each_with_index do |i, idx|
        list.push( [ yield(i), (idx and i) ] )
      end
      list.sort.map { |tmp, (idx and i)| i }
    end
  end
end

# When doing such, the map_with_index
# (or the Iterator) it becomes desired.
#
#(11:49)

=end

----------8<----------------------------------------->8----------

# ============================================================
# Extension for experimentalists  -  $XITER (silly temp. name)
# ============================================================

# You can use $XITER virtual_variable where block_given? applies.
# (Safe anywhere.  Effect will be correct, even if you disagree;)

def x(q)
  3.times do |i|.ix
    printf("\nIter #%i==%i (%i)\n", i, ix, $XITER)
    $XITER += 2
    yield q, i, ix, $XITER
  end
end

x ('roo') do |yq, yi, yix, yiter|.xv
  p [[yq, yi, yix], [yiter, xv], $XITER]
end


#-> Iter #0==0 (0)
#-> [["roo", 0, 0], [2, 2], false]
#->
#-> Iter #1==1 (3)
#-> [["roo", 1, 1], [5, 5], false]
#->
#-> Iter #2==2 (6)
#-> [["roo", 2, 2], [8, 8], false]

----------8<----------------------------------------->8----------

=begin

  Reasons why I left Ruby for another language
  ============================================

  Ruby has powerful iterators but no access to their indexices.
  So I changed to Ruby, which has both!      C OO L.

  Or should that be indicexes ???

  Now my mind is free to explore inject(i) {|a,b|.x 'wheeesh'}



  History (for power readers)
  ===========================

  [CHANGELOG]
  Thu Feb  5 18:58:46 1998  Yukihiro Matsumoto  <matz@netlab.co.jp>

        * enum.c (enum_each_with_index): new method.


  http://www.ruby-talk.org/56121  DAB  - MWI, Inc.
  http://www.ruby-talk.org/56125  Matz - YAGNI
  http://www.ruby-talk.org/56719  Matz
  http://www.ruby-talk.org/56730  Matz
  http://www.ruby-talk.org/56845  _why
  http://www.ruby-talk.org/56939  extending block w/ counter

=end

----------8<----------------------------------------->8----------

# Currently difficult ?

"Hello World!".scan(/[aeiou]./) do |str|.ix
  printf("(%2s) @ %2d\n", str, ix)
end

#-> (el) @  1
#-> (o ) @  4
#-> (or) @  7

----------------------------------------------------->8----------



=================================================================
Implementation Notes
=================================================================
<parse.y>

1) The index variable (xv) is recognised by the parser in a
   similar way to a block parameter.

2) An assignment node is created (L/DASGN) depending on
   whether the xv already exists in an outer scope.

3) When an ITER node is created by the parser and an xv has
   been recognised, pointers to the ASGN node and the ITER
   node are stored in an ITERX node.

   (It would have been easier to extend NODE_ITER by the size
    of a pointer but that could, possibly, extend all ruby
    objects.)

3a) Without xv:        (Normal Ruby)
       NODE_ITER
         u1 'nd_var'   (block params)
         u2 'nd_body'  (block statements)
         u3 'nd_iter'  (func; e.g. each)

3b) With xv:
       NODE_ITERX
         u1 'nd_inode' (-> ITER node)
         u2 'nd_xvasn' (-> L/DASGN)         [would be in u4]
         u3 'nd_iter'  (func; e.g. each)

                  NODE_ITER  (from 'nd_inode' of ITERX above)
                    u1 'nd_var'   (block params)
                    u2 'nd_body'  (block statements)
                    u3  0

=================================================================
<eval.c>

4) When rb_eval sees an ITERX node and the struct BLOCK is
   established,  a pointer to the ASGN node is set in the
   block for easy access and is used in tests for xv presence.
   (Access to the ASGN node via the ITERX node may be lost
    after a ruby 'redo' / 'retry' etc.  Access is maintained
    via the pointer in the BLOCK.)

   [struct BLOCK is necessarily extended by 4 bytes.]

   The value (index count) in the ASGN node is initialised
   to zero at this time.

5) On a yield to the block (rb_yield_0), the value in the
   ASGN node (which also holds the variable's ID) is assigned
   to the index variable.

6) Before rb_yield_0 returns, the index count is incremented
   but will be assigned to the variable only at the start of
   the next iteration.

=================================================================

* Any assignment to the xv from a script will NOT be carried
  over to the next iteration.

* Any iterator method implemented by the interpreter will be
  able to alter the index value before the next iteration.
  For example, String#scan could send the index position of
  each match (currently inaccessible ?) into the block, if
  an xv has been supplied, simply by overwriting the new
  index value before a yield, using ...

   void        rb_xv_assign_long(long);

  String#scan is the only iterator method that has been
  tampered with.  Unmodified, an xv in the block would
  increment from zero like any other but it seemed that
  selected methods might be usefully tweaked ?  (e.g. gsub)

=================================================================




Any need for this ??  (Perhaps in Japan ?)
   (Please pile on the pressure if you need similar fun ;)


 [xv.patch] attached for experiments.   (CVS head 2003/09/02)


(c) Dave Butcher - 2003/09/02  (10:10 +0100)
    attachment is Ruby Licensed


Feel free to criticise but be forewarned that grumbling
about syntax could result in public humiliation from
a prepared, bullet-proof defence (defense) :-)

Thanks:

  Matz             - Ruby
  Nobu             - (for helping poor Win32 users) & speed-patching.
      Ruby Hackers / Library authors.
  PragDave Thomas  - PickAxe & nodeDump & ri & stuff
                      Programming Ruby ...       (K&R === T&H  #-> true)
  PragAndy Hunt    - PickAxe & one-click installer
  Sakazuki-san +   - RDE (Ruby Development Environment)    :(Win32 only)
  Guy Decoux       - Étude posting & fl/ii (nodedump)
    comp.lang.ruby / ruby-talk regulars.
  David Alan Black - Code-brevity, brick wall head-banging &
                      English language guardianship.


daz


[-- Attachment #2: xv.patch --]
[-- Type: application/octet-stream, Size: 11426 bytes --]

diff -u3pPr orig-1.8.0-2003.09.02/eval.c orxv-1.8.0-2003.09.02/eval.c
--- orig-1.8.0-2003.09.02/eval.c	Tue Sep 02 06:52:36 2003
+++ orxv-1.8.0-2003.09.02/eval.c	Tue Sep 02 13:10:30 2003
@@ -106,6 +106,10 @@ static VALUE rb_cUnboundMethod;
 static VALUE umethod_bind _((VALUE, VALUE));
 static VALUE rb_mod_define_method _((int, VALUE*, VALUE));
 
+static VALUE xv_getter _((void));		/* bXV */
+static void  xv_setter _((VALUE));		/* bXV */
+extern void  rb_xv_assign_long _((long));	/* bXV */
+
 static int scope_vmode;
 #define SCOPE_PUBLIC    0
 #define SCOPE_PRIVATE   1
@@ -611,6 +615,7 @@ struct BLOCK {
     VALUE klass;
     NODE *cref;
     int iter;
+    NODE *xvasn;   /* xv ASGN node ptr  bXV */
     int vmode;
     int flags;
     struct RVarmap *dyna_vars;
@@ -639,6 +644,7 @@ static struct BLOCK *ruby_block;
     _block.prev = ruby_block;		\
     _block.outer = ruby_block;		\
     _block.iter = ruby_iter->iter;	\
+    _block.xvasn = 0;	/* bXV */	\
     _block.vmode = scope_vmode;		\
     _block.flags = BLOCK_D_SCOPE;	\
     _block.dyna_vars = ruby_dyna_vars;	\
@@ -2440,6 +2446,7 @@ rb_eval(self, n)
     if (!node) RETURN(Qnil);
 
     ruby_current_node = node;
+
     switch (nd_type(node)) {
       case NODE_BLOCK:
 	if (contnode) {
@@ -2687,6 +2694,7 @@ rb_eval(self, n)
 	result = block_pass(self, node);
 	break;
 
+      case NODE_ITERX:
       case NODE_ITER:
       case NODE_FOR:
 	{
@@ -2697,7 +2705,16 @@ rb_eval(self, n)
 	    if (state == 0) {
 	      iter_retry:
 		PUSH_ITER(ITER_PRE);
-		if (nd_type(node) == NODE_ITER) {
+
+		if (nd_type(node) == NODE_ITERX) {	/* bXV */
+		    ruby_block->var = node->nd_inode->nd_var;
+		    ruby_block->body = node->nd_inode->nd_body;
+		    ruby_block->xvasn = node->nd_xvasn;
+		    ruby_block->xvasn->nd_xvval = INT2FIX(0);  /* bXV Initialize index (zero by convention ;) */
+
+		    result = rb_eval(self, node->nd_iter);
+		}
+		else if (nd_type(node) == NODE_ITER) {
 		    result = rb_eval(self, node->nd_iter);
 		}
 		else {
@@ -3766,6 +3783,7 @@ rb_mod_protected_method_defined(mod, mid
 }
 
 NORETURN(static VALUE terminate_process _((int, const char *, long)));
+
 static VALUE
 terminate_process(status, mesg, mlen)
     int status;
@@ -4081,6 +4099,7 @@ rb_yield_0(val, self, klass, flags, aval
 	ruby_dyna_vars = block->dyna_vars;
     }
     PUSH_CLASS(klass ? klass : block->klass);
+
     if (!klass) {
 	self = block->self;
     }
@@ -4089,6 +4108,7 @@ rb_yield_0(val, self, klass, flags, aval
     if (block->var) {
 	PUSH_TAG(PROT_NONE);
 	if ((state = EXEC_TAG()) == 0) {
+
 	    if (block->var == (NODE*)1) { /* no parameter || */
 		if ((flags & YIELD_PROC_CALL) && RARRAY(val)->len != 0) {
 		    rb_raise(rb_eArgError, "wrong number of arguments (%ld for 0)",
@@ -4140,6 +4160,10 @@ rb_yield_0(val, self, klass, flags, aval
 	if (state) goto pop_state;
     }
 
+    if (block->xvasn) {		/* bXV */
+	assign(0, block->xvasn, block->xvasn->nd_xvval, 0);	/* bXV */
+    }
+
     PUSH_ITER(block->iter);
     PUSH_TAG(PROT_NONE);
     if ((state = EXEC_TAG()) == 0) {
@@ -4209,9 +4233,44 @@ rb_yield_0(val, self, klass, flags, aval
     scope_vmode = old_vmode;
     ruby_current_node = cnode;
     if (state) JUMP_TAG(state);
+
+    if (block->xvasn) {
+	/* bXV - increment index for next iteration */
+	block->xvasn->nd_xvval = INT2FIX((FIX2INT(block->xvasn->nd_xvval))+1);	/* bXV */
+    }
+
     return result;
 }
 
+static VALUE
+xv_getter()		/* bXV */
+{
+    if (rb_block_given_p()) {
+	if (ruby_block->xvasn) {
+	    return ruby_block->xvasn->nd_xvval;
+	}
+    }
+    return Qfalse;
+}
+
+static void
+xv_setter(val)		/* bXV */
+    VALUE val;
+{
+    if (FIXNUM_P(val) && rb_block_given_p()) {
+	if (ruby_block->xvasn) {
+	    ruby_block->xvasn->nd_xvval = val;
+	}
+    }
+}
+
+extern void
+rb_xv_assign_long(val)		/* bXV */
+    long val;
+{
+    xv_setter(INT2FIX(val));
+}
+
 VALUE
 rb_yield(val)
     VALUE val;
@@ -6527,6 +6586,7 @@ Init_eval()
 
     rb_define_virtual_variable("$@", errat_getter, errat_setter);
     rb_define_hooked_variable("$!", &ruby_errinfo, 0, errinfo_setter);
+    rb_define_virtual_variable("$XITER", xv_getter, xv_setter);		/* bXV */
 
     rb_define_global_function("eval", rb_f_eval, -1);
     rb_define_global_function("iterator?", rb_f_block_given_p, 0);
diff -u3pPr orig-1.8.0-2003.09.02/gc.c orxv-1.8.0-2003.09.02/gc.c
--- orig-1.8.0-2003.09.02/gc.c	Fri Aug 22 09:09:56 2003
+++ orxv-1.8.0-2003.09.02/gc.c	Mon Sep 01 08:26:20 2003
@@ -675,6 +675,7 @@ rb_gc_mark_children(ptr)
 	  case NODE_IF:		/* 1,2,3 */
 	  case NODE_FOR:
 	  case NODE_ITER:
+	  case NODE_ITERX:		/* bXV */
 	  case NODE_CREF:
 	  case NODE_WHEN:
 	  case NODE_MASGN:
diff -u3pPr orig-1.8.0-2003.09.02/node.h orxv-1.8.0-2003.09.02/node.h
--- orig-1.8.0-2003.09.02/node.h	Wed Aug 27 20:43:46 2003
+++ orxv-1.8.0-2003.09.02/node.h	Tue Sep 02 01:26:18 2003
@@ -29,6 +29,7 @@ enum node_type {
     NODE_OPT_N,
     NODE_WHILE,
     NODE_UNTIL,
+    NODE_ITERX,
     NODE_ITER,
     NODE_FOR,
     NODE_BREAK,
@@ -194,6 +195,11 @@ typedef struct RNode {
 #define nd_ibdy  u2.node
 #define nd_iter  u3.node
 
+#define nd_inode u1.node  /* bXV -> NODE_ITER  */
+#define nd_xvasn u2.node  /* bXV -> NODE_?ASGN */
+
+#define nd_xvval u2.value /* bXV index value   */
+
 #define nd_value u2.node
 #define nd_aid   u3.id
 
@@ -252,6 +258,7 @@ typedef struct RNode {
 #define NEW_UNTIL(c,b,n) NEW_NODE(NODE_UNTIL,c,b,n)
 #define NEW_FOR(v,i,b) NEW_NODE(NODE_FOR,v,b,i)
 #define NEW_ITER(v,i,b) NEW_NODE(NODE_ITER,v,b,i)
+#define NEW_ITERX(inode,xvasn) NEW_NODE(NODE_ITERX,inode,xvasn,0)
 #define NEW_BREAK(s) NEW_NODE(NODE_BREAK,s,0,0)
 #define NEW_NEXT(s) NEW_NODE(NODE_NEXT,s,0,0)
 #define NEW_REDO() NEW_NODE(NODE_REDO,0,0,0)
diff -u3pPr orig-1.8.0-2003.09.02/parse.y orxv-1.8.0-2003.09.02/parse.y
--- orig-1.8.0-2003.09.02/parse.y	Tue Sep 02 06:50:46 2003
+++ orxv-1.8.0-2003.09.02/parse.y	Tue Sep 02 11:13:12 2003
@@ -61,6 +61,8 @@ NODE *ruby_eval_tree = 0;
 char *ruby_sourcefile;		/* current source file */
 int   ruby_sourceline;		/* current line no. */
 
+int   xv_line;			/* bXV line no. */
+
 static int yylex();
 static int yyerror();
 
@@ -260,6 +262,7 @@ static void top_local_setup();
 %type <node> f_arglist f_args f_optarg f_opt f_block_arg opt_f_block_arg
 %type <node> assoc_list assocs assoc undef_list backref string_dvar
 %type <node> block_var opt_block_var brace_block cmd_brace_block do_block lhs none
+%type <node> opt_block_index  var_index  paren_var_index  /* bXV */
 %type <node> mlhs mlhs_head mlhs_basic mlhs_entry mlhs_item mlhs_node
 %type <id>   fitem variable sym symbol operation operation2 operation3
 %type <id>   cname fname op f_rest_arg
@@ -651,13 +654,15 @@ block_command	: block_call
 cmd_brace_block	: tLBRACE_ARG
 		    {
 			$<vars>$ = dyna_push();
-			$<num>1 = ruby_sourceline;
+			$<num>1 = xv_line = ruby_sourceline;
 		    }
-		  opt_block_var {$<vars>$ = ruby_dyna_vars;}
+		  opt_block_var   {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;}	/* bXV */
+		  opt_block_index {$<vars>4 = ruby_dyna_vars;}					/* bXV */
 		  compstmt
 		  '}'
 		    {
-			$$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+			$$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+			if ($5) $$ = NEW_ITERX($$, $5);			/* bXV */
 			nd_set_line($$, $<num>1);
 			dyna_pop($<vars>2);
 		    }
@@ -1733,16 +1738,44 @@ opt_block_var	: none
 		    }
 		;
 
+paren_var_index	: '(' /* none */ ')'
+		    {
+			$$ = 0;
+		    }
+		| '(' var_index ')'
+		    {
+			$$ = $2;
+		    }
+		;
+
+opt_block_index	: none
+		| '.'  var_index
+		    {
+			if (xv_line != ruby_sourceline) goto xv_Err_lbfd;
+			$$ = $2;
+		    }
+		| '.'  paren_var_index
+		    {
+			if (xv_line != ruby_sourceline) {
+		     xv_Err_lbfd:
+			    rb_warn("break in line %i", xv_line);
+			}
+			$$ = $2;
+		    }
+		;
+
 do_block	: kDO_BLOCK
 		    {
 		        $<vars>$ = dyna_push();
-			$<num>1 = ruby_sourceline;
+			$<num>1 = xv_line = ruby_sourceline;
 		    }
-		  opt_block_var {$<vars>$ = ruby_dyna_vars;}
+		  opt_block_var   {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;}	/* bXV */
+		  opt_block_index {$<vars>4 = ruby_dyna_vars;}					/* bXV */
 		  compstmt
 		  kEND
 		    {
-			$$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+			$$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+			if ($5) $$ = NEW_ITERX($$, $5);			/* bXV */
 			nd_set_line($$, $<num>1);
 			dyna_pop($<vars>2);
 		    }
@@ -1799,24 +1832,28 @@ method_call	: operation paren_args
 brace_block	: '{'
 		    {
 		        $<vars>$ = dyna_push();
-			$<num>1 = ruby_sourceline;
+			$<num>1 = xv_line = ruby_sourceline;
 		    }
-		  opt_block_var {$<vars>$ = ruby_dyna_vars;}
+		  opt_block_var   {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;}	/* bXV */
+		  opt_block_index {$<vars>4 = ruby_dyna_vars;}					/* bXV */
 		  compstmt '}'
 		    {
-			$$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+			$$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+			if ($5) $$ = NEW_ITERX($$, $5);			/* bXV */
 			nd_set_line($$, $<num>1);
 			dyna_pop($<vars>2);
 		    }
 		| kDO
 		    {
 		        $<vars>$ = dyna_push();
-			$<num>1 = ruby_sourceline;
+			$<num>1 = xv_line = ruby_sourceline;
 		    }
-		  opt_block_var {$<vars>$ = ruby_dyna_vars;}
+		  opt_block_var   {$<vars>$ = ruby_dyna_vars; xv_line = ruby_sourceline;}	/* bXV */
+		  opt_block_index {$<vars>4 = ruby_dyna_vars;}					/* bXV */
 		  compstmt kEND
 		    {
-			$$ = NEW_ITER($3, 0, dyna_init($5, $<vars>4));
+			$$ = NEW_ITER($3, 0, dyna_init($7, $<vars>4));
+			if ($5) $$ = NEW_ITERX($$, $5);			/* bXV */
 			nd_set_line($$, $<num>1);
 			dyna_pop($<vars>2);
 		    }
@@ -2160,6 +2197,33 @@ var_lhs		: variable
 		    }
 		;
 
+var_index	: tCONSTANT	{ goto xv_Err_bimbl; }
+		| tIVAR		{ goto xv_Err_bimbl; }
+		| tGVAR		{ goto xv_Err_bimbl; }
+		| tCVAR		{ goto xv_Err_bimbl; }
+		| tIDENTIFIER
+		    {
+			if (!is_local_id($1)) {
+		    xv_Err_bimbl:
+			    yyerror("block index must be a local variable");
+			}
+			if (rb_dvar_curr($1)) {
+			    yyerror("duplicate name for block parameter / index variable");
+			}
+
+			if (rb_dvar_defined($1)) {
+			    $$ = NEW_DASGN($1, 0);
+			}
+			else if (local_id($1) || !dyna_in_block()) {
+			    $$ = NEW_LASGN($1, 0);
+			}
+			else{
+			    rb_dvar_push($1, 0);
+			    $$ = NEW_DASGN_CURR($1, 0);
+			}
+		    }
+		;
+
 backref		: tNTH_REF
 		| tBACK_REF
 		;
@@ -2541,6 +2605,7 @@ yycompile(f, line)
     n = yyparse();
     ruby_debug_lines = 0;
     compile_for_eval = 0;
+
     ruby_in_compile = 0;
     cond_stack = 0;
     cmdarg_stack = 0;
diff -u3pPr orig-1.8.0-2003.09.02/string.c orxv-1.8.0-2003.09.02/string.c
--- orig-1.8.0-2003.09.02/string.c	Wed Aug 27 20:43:46 2003
+++ orxv-1.8.0-2003.09.02/string.c	Sat Aug 30 20:38:30 2003
@@ -3066,6 +3066,7 @@ rb_str_scan(str, pat)
     while (!NIL_P(result = scan_once(str, pat, &start))) {
 	match = rb_backref_get();
 	rb_match_busy(match);
+	rb_xv_assign_long(start - RSTRING(result)->len);		/* bXV trick */
 	rb_yield(result);
 	rb_backref_set(match);	/* restore $~ value */
     }

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
  2003-09-02 14:48 daz
@ 2003-09-02 15:11 ` Gavin Sinclair
  2003-09-02 15:58   ` daz
  2003-09-02 17:01 ` why the lucky stiff
  1 sibling, 1 reply; 11+ messages in thread
From: Gavin Sinclair @ 2003-09-02 15:11 UTC (permalink / raw
  To: daz

On Wednesday, September 3, 2003, 12:48:23 AM, daz wrote:


> Hello 'core,

> My first post here, so I'll try not to be too controversial.
> It might be too long for busy people.

> =begin

>   [RCR] proposal - optional block index variable
>   ==============================================


Hi Dave,

I like the idea, and read nearly every word (skipping some
implementation details) of your long post.  I have some concern about
the special treatment of the index variable in String#scan that you
propose.  A nice rule doesn't look so nice when there are exceptions.

Cheers,
Gavin

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
  2003-09-02 15:11 ` Gavin Sinclair
@ 2003-09-02 15:58   ` daz
  0 siblings, 0 replies; 11+ messages in thread
From: daz @ 2003-09-02 15:58 UTC (permalink / raw
  To: ruby-core


----- Original Message ----- 
From: "Gavin Sinclair" <gsinclair@soyabean.com.au>
To: "daz" <ruby-core@ruby-lang.org>
Sent: Tuesday, September 02, 2003 4:11 PM
Subject: Re: |rcr|.xv Index Variables ( *_with_index )


> On Wednesday, September 3, 2003, 12:48:23 AM, daz wrote:
> 
> 
> > Hello 'core,
> 
> > My first post here, so I'll try not to be too controversial.
> > It might be too long for busy people.
> 
> > =begin
> 
> >   [RCR] proposal - optional block index variable
> >   ==============================================
> 
> 
> Hi Dave,
> 
> I like the idea, and read nearly every word (skipping some
> implementation details) of your long post.  I have some concern about
> the special treatment of the index variable in String#scan that you
> propose.  A nice rule doesn't look so nice when there are exceptions.
> 
> Cheers,
> Gavin
> 

So true.  Scan was an obvious example to show that the rule
could be bent if concensus were to go that way.
string.c is a one-line change and an afterthought rather than
in the design.  It wouldn't bother me you decided to boot it.

Any change has got to be right, if there is a right.

Hope you don't mind if I post a link:
http://www.d10.karoo.net/ruby/xv/xv.patch
because I sent in MIME format (the gotcha for list newbies
coming from Usenet).
(I don't know if my ISP allows FTP access, sorry.)


Thanks, Gavin.


daz

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
  2003-09-02 14:48 daz
  2003-09-02 15:11 ` Gavin Sinclair
@ 2003-09-02 17:01 ` why the lucky stiff
  2003-09-03 13:50   ` daz
  1 sibling, 1 reply; 11+ messages in thread
From: why the lucky stiff @ 2003-09-02 17:01 UTC (permalink / raw
  To: ruby-core

daz (dooby@d10.karoo.co.uk) wrote:
> 
> loop {.x
>   x < 5 or break
>   p x
> }
> 

daz:

A good, exhaustive trip through this particular debacle.  Your citations
were cool.

You know, I think our basic problem concerns metadata.  An index is
merely metadata applicable to an iteration cycle.  And I don't think
that's the only bit of metadata that could be useful in iteration.  The
total number of iterations expected.  Even an available reference to the
current block would be great for simpler recursion.

I think you've done a good job presenting your RCR (although it's really
hard to push syntactical changes with the current parser.)

My concerns:

  1. If we do want to add more metadata, the syntax becomes cumbersome.
     I'm guessing additions would be positional.

       loop {.index.last print "Item #{ index } of #{ last }." }

     If there is no plan for expansion of the syntax, then it become an
     _exception_ to Ruby's syntax rather than an _evolution_ of Ruby's
     syntax.

  2. The dot notation looks like a method call.  Yet, assignment is
     being performed.  It also gives the illusion that the block's
     argument list is an object.

I would love to see a good solution to this problem, though.  So I
wonder:  is there a way we can provide access to block metadata?

Perhaps through a keyword, which provides an instance of metadata within
a block:

  loop {
    x = iter.index
    x < 5 or break
    p x
  }

Or maybe a generic metadata scheme which could be used with various Ruby
constructs:

  class Test
    [dot_net_signature => [String, Int, Int]]
    def run_me( a, b )
      "#{ a } + #{ b }"
    end
  end

  Test.method( :run_me ).meta['dot_net_signature'] => [String, Int, Int]

  # Start our index at 2..
  [index => 2]
  loop {
    x = meta['index']
    x < 5 or break
    p x
  }

Fun ideas, but you have to think about scoping and the various types of
blocks that commonly float around in Ruby.

_why

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
@ 2003-09-02 18:13 george.marrows
  2003-09-02 22:43 ` Gavin Sinclair
  2003-09-03 13:39 ` daz
  0 siblings, 2 replies; 11+ messages in thread
From: george.marrows @ 2003-09-02 18:13 UTC (permalink / raw
  To: ruby-core

> I like the idea, and read nearly every word (skipping some
> implementation details) of your long post.  I have some concern about
> the special treatment of the index variable in String#scan that you
> propose.  A nice rule doesn't look so nice when there are exceptions.
> 
> Cheers,
> Gavin

What about hashes? Could/should the keys get passed to the index variable?
That way the scan exceptions start to look like a general rule: the index
variable is whatever makes most sense for the given method. 

Isn't it effectively just another arg yielded by an iterator, the difference
being that the block only needs to bother with it if it's interested. A neat
kind of optional arg for blocks, in other words ...

Dave - how would write a method in Ruby which supplied an index variable?

-- George 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
  2003-09-02 18:13 george.marrows
@ 2003-09-02 22:43 ` Gavin Sinclair
  2003-09-03 13:39 ` daz
  1 sibling, 0 replies; 11+ messages in thread
From: Gavin Sinclair @ 2003-09-02 22:43 UTC (permalink / raw
  To: george.marrows@ps.ge.com

On Wednesday, September 3, 2003, 4:13:48 AM, george wrote:

>> I like the idea, and read nearly every word (skipping some
>> implementation details) of your long post.  I have some concern about
>> the special treatment of the index variable in String#scan that you
>> propose.  A nice rule doesn't look so nice when there are exceptions.
>> 
>> Cheers,
>> Gavin

> What about hashes? Could/should the keys get passed to the index variable?
> That way the scan exceptions start to look like a general rule: the index
> variable is whatever makes most sense for the given method. 

> Isn't it effectively just another arg yielded by an iterator, the difference
> being that the block only needs to bother with it if it's interested. A neat
> kind of optional arg for blocks, in other words ...

> Dave - how would write a method in Ruby which supplied an index variable?

My understanding is that it's an automatic block-argument.  It is
meaningless to attempt to assign values to it.  Any block can use it
or ignore it if it wishes.  For example:

  def transaction(*args)
    start
    yield(*args)
    commit
  end

  transaction(foo) do |f|.i
    puts "Index: #{i}"
  end

  # Output: "Index: 0"

That example does not demonstrate the utility of the proposal; it
demonstrates the automatic way in which the index parameter is set.

Gavin

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
  2003-09-02 18:13 george.marrows
  2003-09-02 22:43 ` Gavin Sinclair
@ 2003-09-03 13:39 ` daz
  1 sibling, 0 replies; 11+ messages in thread
From: daz @ 2003-09-03 13:39 UTC (permalink / raw
  To: ruby-core


From: <george.marrows@ps.ge.com>


> 
> What about hashes? Could/should the keys get passed to the index variable?

No, because you can use any current iterator which passes
the key as a block parameter and use the xv if you have a
need for it.  That's all.

 hsh = {'a'=>1, 'b'=>2}

 hsh.each_with_index {|e,    ix| p [ e[0], e[1], ix ]}
 hsh.each            {|k, v|.ix  p [ k   , v   , ix ]}

These lines have eqivalent output but there are no
equivalents for #fetch, #reject, #each_key etc. in
a similar situation.

> That way the scan exceptions start to look like a general rule: the index
> variable is whatever makes most sense for the given method. 

It does seem to make sense when scan is iterating internally on
the bytes in a string but only yielding to its block when it
finds some useful information (a match).  Externally (in the block),
it could be argued that (iter+1) is bogus information.

> 
> Isn't it effectively just another arg yielded by an iterator, the difference
> being that the block only needs to bother with it if it's interested.

Sort of but (as Gavin said) it's 'automatic'.  Adding another argument
would need a change to the yield in the method and to any block
which may get attached to the method.

> 
> Dave - how would write a method in Ruby which supplied an index variable?

As an afterthought, I used the Ruby API's  rb_define_virtual_variable()
described in PickAxe:

  Exports a virtual variable to Ruby namespace as the global $name.
  No actual storage exists for the variable; attempts to get and set
  the value will call the given functions.

So, $XITER  will get, $XITER=  will set the index variable associated
with the block which is in scope where the virtual var is used.

def roo
  # ...
  $XITER = 6
  # ...
  yield 'xx', 'yy'
  # ...
end

roo do |a, b|.xv
  p [xv, a, b]
end

#-> [6, "xx", "yy"]

I'd encourage you to apply the patch and try things out.
I'm quite confident that it won't upset your normal work
because of its narrow focus.
Any block which doesn't have an xv has a zero pointer
which prevents an interference from the tiny changes
to the interpreter.

> 
> -- George 
> 


daz

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
  2003-09-02 17:01 ` why the lucky stiff
@ 2003-09-03 13:50   ` daz
  0 siblings, 0 replies; 11+ messages in thread
From: daz @ 2003-09-03 13:50 UTC (permalink / raw
  To: ruby-core


From: "why the lucky stiff" <ruby-core@whytheluckystiff.net>

> 
> You know, I think our basic problem concerns metadata.  An index is
> merely metadata applicable to an iteration cycle.  And I don't think
> that's the only bit of metadata that could be useful in iteration.  The
> total number of iterations expected.  Even an available reference to the
> current block would be great for simpler recursion.
> 

Selectively quoting from your post http://www.ruby-talk.org/56845
-------------------------------------------------------------------------
It would be really cool if, instead of having a million *_with_index
methods floating around in Enumerable, we had a method which could be
used inside a block to retrieve the number of iterations that the block
has undergone.

[code snip]

The `index' method can then be used in the block:

  c = z.collect { |el| f(el, a[index], b[index]) }
-------------------------------------------------------------------------

def f(r, s, t)
  [r, s, t]
end

a = [10, 11, 12]
b = [20, 21, 22]
z = %w{a b c}

c = z.collect { |el|.index  f(el, a[index], b[index]) }
p c

#-> [["a", 10, 20], ["b", 11, 21], ["c", 12, 22]]

This, I feel sure you're going to tell me :-), isn't what you wanted.


> I think you've done a good job presenting your RCR (although it's really
> hard to push syntactical changes with the current parser.)

Thanks.
To push syntactical changes to Matz or ... ??

I really expected the journey into yacc to be the more difficult
of the two parts (173 reduce/reduce conflicts, first attempt;
you know the score), but it slotted it quite nicely.

> 
> My concerns:
> 
>   1. If we do want to add more metadata, the syntax becomes cumbersome.

If we wanted metadata, we wouldn't do it by passing a Fixnum.

>      I'm guessing additions would be positional.

There is no metadata for a simple index, so no additions.
I'm following, all the same :-)

> 
>        loop {.index.last print "Item #{ index } of #{ last }." }
> 

loop {.(index,last) print "Item #{ index } of #{ last }." }

 would cope but that would be getting ugly.  If you wanted
metadata (which included the block index) you would only
need one parameter {.meta } and you'd probably want another valuable
piece of data included - the variables passed to the block.

I don't think meta[:index] is any improvement on 'with_index'.

>      If there is no plan for expansion of the syntax, then it become an
>      _exception_ to Ruby's syntax rather than an _evolution_ of Ruby's
>      syntax.

AFAIK, there is no plan to expand the |parm1, parm2| syntax to be
used anywhere other than at the head of a block definition.
Both are positional.

> 
>   2. The dot notation looks like a method call.  Yet, assignment is
>      being performed.

3.14159265358979

Method call on Fixnum 3 ??

Familiarity is what dispels that illusion and no-one can be
familiar with something that they haven't been introduced to.

I used the notation in a position where it couldn't possibly be
mistaken for a method call.  It follows 'do', '{' or '|'.

>  It also gives the illusion that the block's
>      argument list is an object.
> 

And the reason that it's not an object, I suspect, is  one of
practicality.

2.times do
  puts meta[:vars[0]]
end

> 
> Fun ideas, but you have to think about scoping and the various types of
> blocks that commonly float around in Ruby.

An index variable has the same scope as a block parameter.
As far as I can discern from the code, an xv will pop in and out of
scope if it's passed, converted to a proc or whatever you'd normally
want to do.  Ruby takes care of all that, not me.

> 
> _why
> 
> 

Thanks for your input and I appreciate that many of your
comments were directed towards the wider discussion.

It would take a small change to xv.patch to initialise your
own Ruby object (class BlockMeta ?) where I've assigned
Fixnum 0, which you could refresh where I've incremented.
Your object would be available in the block via the xv
and you could call your methods on it.

1.times do .x
  p x.class
end

#-> Fixnum        (could be BlockMeta)


daz

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
@ 2003-09-03 14:14 george.marrows
  2003-09-03 18:05 ` daz
  0 siblings, 1 reply; 11+ messages in thread
From: george.marrows @ 2003-09-03 14:14 UTC (permalink / raw
  To: ruby-core

(Mail originally sent privately by mistake - sorry for any confusion &
apologies for Outlook wrecking the threading.)

> > Dave - how would write a method in Ruby which supplied an 
> > index variable?
> 
> My understanding is that it's an automatic block-argument.  It is
> meaningless to attempt to assign values to it.  Any block can use it
> or ignore it if it wishes.  For example:
> 
>   def transaction(*args)
>     start
>     yield(*args)
>     commit
>   end
> 
>   transaction(foo) do |f|.i
>     puts "Index: #{i}"
>   end
> 
>   # Output: "Index: 0"
> 
> That example does not demonstrate the utility of the proposal; it
> demonstrates the automatic way in which the index parameter is set.

But the scan and gsub (and possibly the hash key) examples show that maybe
it doesn't have to just be the loop count. Dave provides a C api for setting
the index value, so perhaps there should be a mechanism/syntax for setting
it from Ruby code. It'd be very annoying to have library methods which can
set the index variable in an interesting way but not to be able to set
yourself without C programming.

BTW what about eg 'Hello'.each_byte {|ch;ix| puts ch} for the syntax? And of
course {|;ix| } for the index only.

-- George

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
  2003-09-03 14:14 george.marrows
@ 2003-09-03 18:05 ` daz
  0 siblings, 0 replies; 11+ messages in thread
From: daz @ 2003-09-03 18:05 UTC (permalink / raw
  To: ruby-core


From: <george.marrows@ps.ge.com>


> Dave provides a C api for setting the index value, so perhaps
> there should be a mechanism/syntax for setting it from Ruby
> code.


$XITER (horrible temporary name chosen by me to avoid clashes:)
gives access to a block's index from a script.

I wasn't expecting any interest in that.

It looks like a global var but it's local to the current scope
like $_ and some of the pattern-matching variables ($&, $' etc.).

Rather than changing the xv, send your value with yield.
If an xv is given, it will contain the default incrementing
value.

def roo
  3.times do .x
    yield 2*x
  end
end

roo {|r1|.x p [x, r1]}

#-> [0, 0]
#-> [1, 2]
#-> [2, 4]


> BTW what about eg 'Hello'.each_byte {|ch;ix| puts ch} for the syntax?
> And of course {|;ix| } for the index only.
> 
> -- George

My thinking was that the index isn't a block parameter [b/p]
(it doesn't affect the arity) so why must it be inside
the bars (which are delimeters for the b/ps) ?

However, your use of a single character is very much in line
with the spirit I was trying to capture.


daz

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: |rcr|.xv  Index Variables ( *_with_index )
@ 2003-09-04  8:59 george.marrows
  0 siblings, 0 replies; 11+ messages in thread
From: george.marrows @ 2003-09-04  8:59 UTC (permalink / raw
  To: ruby-core

> I hope you saw from my reply to your post that $XITER
> uses the "C" API but it's available to you through
> that thing (which looks an awful lot like a global variable).
> It isn't a global.

Yes, I saw that - thanks. I'll try to give a bit more thought to syntax and
the hash key suggestion that I raised, but I'm off on holiday shortly so
it'll be some time before anything gets to you. (If it ever does .. :)

Thanks for your posts to ruby-talk btw: always relevant and often very witty
- they lively the place up a bit!

-- George

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2003-09-04  8:52 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2003-09-04  8:59 |rcr|.xv Index Variables ( *_with_index ) george.marrows
  -- strict thread matches above, loose matches on Subject: below --
2003-09-03 14:14 george.marrows
2003-09-03 18:05 ` daz
2003-09-02 18:13 george.marrows
2003-09-02 22:43 ` Gavin Sinclair
2003-09-03 13:39 ` daz
2003-09-02 14:48 daz
2003-09-02 15:11 ` Gavin Sinclair
2003-09-02 15:58   ` daz
2003-09-02 17:01 ` why the lucky stiff
2003-09-03 13:50   ` daz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).