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

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

Couldn’t currying handle most of this?

said on

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

said on
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

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

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

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

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

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

said on

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

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

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

said on

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

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

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

said on

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

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

said on

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

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

said on

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

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

stop the madness!

My head hurts enough from template metaprogramming. :)

said on

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

Anything you can do, we can do meta.

said on DD Mon YYYY at HH:MM

* do fancy stuff in your comment.

PREVIEW PANE