hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Counting At The Cloak 'N' Bind #

by why in inspect

So Method#to_proc is basically totally bankrupt. It is now the most severely mocked typecast in the book. From RailsConf:

 [10, 20, 30].map &4.method(:+)

 ary.each &(hsh={}).method(:store)

 def initialize(hsh)
   hsh.each &method(:instance_variable_set)
 end

Clearly, fun stuff. But, you know, bankrupt.

If you don’t mind drawing a bit more blood, did you know you can use this stuff to kill (\w+)_with_index?

 %w[joe tim francis].map &with_index { |x| [x, index] }
  #=> [["joe", 0], ["tim", 1], ["francis", 2]]

Here’s how it works: the index accessor is added to the Proc. And each time we loop, the accessor is incremented.

It takes some glue, though. Proc#bind (from Rails, originally cloaker) is used to turn the block into a method of itself! Then with_index adds an incrementing wrapper over it. The Method#to_proc happens when you amp it into each.

 # from activesupport
 class Proc
   def bind(object)
     block, time = self, Time.now
     (class << object; self end).class_eval do
       method_name = "__bind_#{time.to_i}_#{time.usec}" 
       define_method(method_name, &block)
       method = instance_method(method_name)
       remove_method(method_name)
       method
     end.bind(object)
   end
 end

 # there are three interesting variables here:
 # blk:: the block you've passed in.
 # bound:: the same block, but now a method of itself.
 # wrap:: the wrapper proc which does the counting.
 def with_index(&blk)
   class << blk
     attr_reader :index
     def even; @index % 2 == 0 end
     def odd;  @index % 2 == 1 end
   end

   i = 0
   bound = blk.bind(blk)
   wrap = proc do |*a|
     blk.instance_variable_set('@index', i)
     x = bound[*a]
     i += 1
     x
   end
   wrap
 end

Now, for some exercises:

 >> %w[joe tim francis].map &with_index { |x| [x, index] }
 => [["joe", 0], ["tim", 1], ["francis", 2]]

 >> %w[badger goat mule eagle shark].select &with_index { even }
 => ["badger", "mule", "shark"]

However, 1.9’s Enumerator#with_index is definitely less boggling and sloppy. If you have instance variables inside the block, they’ll be lookin in the block object for a home.

said on 27 Jun 2006 at 22:42

If all we want is to pass arguments to Array#map (and that’s what every to_proc example I’ve seen has done), let’s just modify Array#map instead of introducing new and ugly code constructs that make me not want to program in Ruby any more:

class Array
   def map(*args)
      array = []
      hash = {}
      current = nil
      args.each{ |arg|
         if arg.is_a?(Symbol)
            hash[arg] = []
            current = arg
         else
            hash[current] << arg
         end
      }

      self.each{ |e|
         hash.each{ |m, params|
            unless params.empty?
               e = e.send(m, *params)
            else
               e = e.send(m)
            end
         }
         yield e if block_given?
         array << e
      }

      array
   end
end

The above code assumes that non-symbol arguments are meant as parameters to the most immediately previous symbol.

Downcase, then reverse each string:

array = %w/FOO BAR BAZ/
array.map(:downcase, :reverse) # ['oof', 'rab', 'zab']

Add 4, then double, each element of the array:

array = [10, 20, 30]
array.map(:+, 4, :*, 2) # [28, 48, 68]

Yes, I’m sure my map implementation could stand some major refactoring.

said on 27 Jun 2006 at 23:30

Couldn’t currying handle most of this?

said on 28 Jun 2006 at 01:38

makes me start thinking about ‘generator’. is generator in 1.9 still using continuations and being too slow to have fun with?

said on 28 Jun 2006 at 01:57
also, i start wondering why in 1.8.4 this doesn’t do anything useful: .enum_for(:map!).each_with_index{ not only does the each_with_index return a Enumerator object instead of the output of map, it fails to filter the results of its block back into map. so sad.
said on 28 Jun 2006 at 02:06

Daniel Berger: I can’t seem to figure out why you read this blog.

Danno: Not really. Look back at how index and even are used in the last examples.

jes5199: Maybe swap like: .enum_for(:each_with_index).map ...I dunno.

said on 28 Jun 2006 at 02:21

jes5199: there is still a lot of road to walk to get serious generators support in ruby, but IIRC at the moment it does not use callcc anymore, relying instead on a thread-based implementation.

Which recalls me we should convince ko1 to provide M:N threading, otherwise doing this tricks with OS threads may be sad.

said on 28 Jun 2006 at 02:32

Is to_proc still alive!? Another…

class Hash def to_proc Proc.new { |o| each { |k,v| o.send( ”#{k}=”, v ) } } end end

  1. rap

class X attr_accessor :a def initialize( &h ) h[self] end end

X.new(&{:a=>1})

said on 28 Jun 2006 at 02:35

Is to_proc still alive!? Another…

  class Hash
    def to_proc
      Proc.new { |o|
        each { |k,v| o.send( ”#{k}=”, v ) }
      }
    end
  end

  class X 
    attr_accessor :a 
    def initialize( &h )
      h[self]
    end
  end

  X.new(&{:a=>1})
said on 28 Jun 2006 at 09:10

I didn’t understand a single line. Where can I enlighten me?

said on 28 Jun 2006 at 09:39

I’m not really a fan of M:N threading to be honest. And besides, OS threads are already M:N on many platforms.

I think we’re better off combining threads with explicit support for first-class coroutines (i.e. not thread- or continuation- based), like GNU pth threads or Win32 fibers.

said on 28 Jun 2006 at 10:03

I don’t know … the Kernel.with_index thing has something going for it, although as far as the specifics go I’m starting to think I’d prefer something like this:


class Integer
  def even? ; ( self & 1 ).zero? ; end
  def odd? ; ( self & 1 ).nonzero? ; end
end

def with_index( &block )
  index = -1
  Proc.new { |*a| block[ a, index += 1 ] }
end

But, here’s another thing: why don’t we have Integer#even? and Integer#odd? anyhow?

said on 28 Jun 2006 at 10:06

(I realize that misses the point of exercising Method#to_proc though)

said on 28 Jun 2006 at 10:12

Confused: You poor guy, I’m sorry. I really do regret posting this one.

I wouldn’t try to understand the insides of the Proc#bind method. It’s a completely complicated hack. If you happen to know the Prototype JavaScript library, it works just like the bind method in Prototype.

Basically, when you bind a proc to an object, whenever that proc gets called, it’ll act like a method (with instance variables and self and all that.)

 add = proc { |x| self + x }
 add = add.bind("=> ")
 ['pizzicato five', 'deerhoof'].map &add

The with_index binds the Proc to give it index, even and odd methods.

trans: Oh, man, you’re givin me carnal thoughts here…

 class Hash
   def to_proc
     proc { |a|
       self.inject(nil) { |_,(k,v)|
         eval("proc { |#{k}| #{v} }")[*a]
       }
     }
   end
 end

 [[2,4],[4,5],[12,1]].map &{ %|x,y| => %{x + y} }
said on 28 Jun 2006 at 10:18

Okay, here’s one for the party.


class Object
  def to_proc
    method( :<< ).to_proc
  end
end

items = %w( many good steaks )

array = []
items.each &array
p array

string = "" 
items.each &string
p string

items.each &$stdout
puts
said on 28 Jun 2006 at 10:22

why: Okay, that’s just bent. Wow.

said on 28 Jun 2006 at 12:46

why: I read your blog because I find it interesting, as well as many of the responses.

I’m just trying to figure out why people are getting so excited about this convoluted syntax when it seems to me that they’ve never bothered to ask themselves what problem it is that they’re solving.

said on 28 Jun 2006 at 12:52

why: what happens when your hash has more than one pair?

said on 28 Jun 2006 at 13:54

jes5199: it ignores all but one of them—whichever one happens to come last in the iteration. Note how the injection state argument is ignored.

said on 28 Jun 2006 at 13:58

Dan: We’re playing, basically. Exploring. This stuff’s fun! It doesn’t have to solve anything.

said on 28 Jun 2006 at 14:01

Uh, both of those questions were directed to why, weren’t they? Geez, sorry. I don’t know what’s with me this week.

said on 28 Jun 2006 at 16:44

berger: i dont understand this post either. but its giving me perl flashbacks ;)

and it seems the commentbox thinks perl is a typo..coincidence? :D

said on 28 Jun 2006 at 18:02

stop the madness!

My head hurts enough from template metaprogramming. :)

said on 28 Jun 2006 at 19:16

oh! i was making a mistake: why’s code explodes if you use %|a| in your own block, as he is already using |a| in his metablock

said on 30 Jun 2006 at 14:03

Anything you can do, we can do meta.

12 Jul 2010 at 22:16

* do fancy stuff in your comment.

PREVIEW PANE