New magical version of Symbol.to_proc

[Posted at
http://drnicwilliams.com/2006/09/28/new-magical-version-of-symbolto_proc/]

Before the magic, letâ??s go through a Beginnerâ??s Guide to Mapping, then
Advanced Guide to Symbol.to_proc, and THEN, the magical version. Its
worth it. Its sexy, nifty AND magical all at once.

  • Beginnerâ??s Guide to Mapping

list = [â??1â?², â??2â?², â??3â?²]
=> ["1â?³, "2â?³, "3â?³]

list.map {|item| item.to_i}
=> [1, 2, 3]

Here we’re invoking to_i on each item of the list and returning the
result into a new list. That’s map/collect for you.

  • Advanced Guide to Symbol.to_proc

After doing that a few times, you start wishing there was simpler
syntax. Enter: Symbol.to_proc

list.map &:to_i
=> [1, 2, 3]

It works. Just enjoy it. (see article for links on why it works)

  • Magical version of Symbol.to_proc

Quite frankly, thatâ??s still a lot of syntax. Plus, I normally forget to
added parentheses around the &:to_i, and then latter I want to invoke
another method on the result, so I need to add the parentheses which is
a painâ?¦ anyway. I thought of something niftier and dare I say, more
magical.

How about this syntax:

list.to_is
=> [1, 2, 3]

By passing the plural version of a method, the array automagically
performs the above mapping on itself using the singular version of the
method.

Sexy! And here’s more examples:

(1…10).to_a.to_ss
=> [“1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”, “10”]

(1…10).to_a.days
=> [86400, 172800, 259200, 345600, 432000, 518400, 604800, 691200,
777600, 864000]

[2,‘two’, :two].classes
=> [Fixnum, String, Symbol]

[2,‘two’, :two].classes.names
=> [“Fixnum”, “String”, “Symbol”]

[2,‘two’, :two].classes.names.lengths
=> [6, 6, 6]

So much happy syntax in one place!

I’ve got the library that gives you this syntax here:
http://drnicwilliams.com/2006/09/28/new-magical-version-of-symbolto_proc/
(at the bottom)

Nic

Note: the Symbol.to_proc is currently in the Rails libraries
(active_support gem) but will be added into Ruby 1.9 one day in the
future.

Hi –

On Thu, 28 Sep 2006, Dr Nic wrote:

list.map &:to_i
=> [1, 2, 3]

It works. Just enjoy it. (see article for links on why it works)

It’s not my favorite construct; it’s a bit ugly and terse in a bad
way. You mentioned elsewhere that this is going to be in 1.9, though
I remember Matz rejecting an RCR for:

list.map(:to_i)

and &:to_i seems worse :slight_smile:

=> [6, 6, 6]

So much happy syntax in one place!

Actually the reason this is kind of cool is not the syntax but the
semantics. It’s much more expressive than any number of &:->()…
things that have been proposed – and looks better. (There may be
some connection there too.)

I wonder, though, whether it would be too easy for it to trample on
other method names. I guess that’s true of anything – you’d just
have to make sure you didn’t have two conceptions of some plural
thing.

David

Actually the reason this is kind of cool is not the syntax but the
semantics. It’s much more expressive than any number of &:->()…
things that have been proposed – and looks better. (There may be
some connection there too.)

Agreed - its very nice semantically too. It has much more meaning than
.map(&:method_name) I think.

I wonder, though, whether it would be too easy for it to trample on
other method names. I guess that’s true of anything – you’d just
have to make sure you didn’t have two conceptions of some plural
thing.

The method_missing code will perform “super” first to ensure that any
per-existing dynamic methods on the Array are evaluated first. Then it
attempts to call the method on the collection items. If they return a
NoMethodError then the original error object is returned. I think that’s
cleanest.

But yes, its a buyer-beware situation. So - “Smart Buyers Only”!

> > => ["Fixnum", "String", "Symbol"] > I wonder, though, whether it would be too easy for it to trample on > other method names. I guess that's true of anything -- you'd just > have to make sure you didn't have two conceptions of some plural > thing.

I agree, cool and somehow dangerous, I would very conservatively
propose
a proxy method, similiar in spirit to BDD’s rspec#is

%w{the meaning of 42}.map.length => [3, too_long_to_count, 2, 2]

or maybe not overload #map without params for that purpose?

Just an idea of course :wink:

BTW Using English semantics for Ruby semantics does seem

  • interesting
  • avantguarde
  • intolerant
  • too dangerous
  • too early

it is great people doing this, I would hate having it in the core
though.
Robert

Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

Arrrrghh
I was quoting David; who was quoting Nic, sorry for my mistake
Robert

Hi –

On Thu, 28 Sep 2006, Robert D. wrote:

[2,‘two’, :two].classes.names

or maybe not overload #map without params for that purpose?

I personally dislike “magic dot” stuff like that. True, it’s not
really magic, since map can return some kind of enumerator that knows
about the array and is ready to do something to it. But it still has
the look and feel of a kind of secret system for communicating what’s
wanted, rather than something that really makes sense when read left
to right.

That’s why I prefer Dr. Nic’s plurals thing, though it definitely has
the disadvantage of entering into murky territority in terms of method
naming.

David

Robert D. wrote:

I agree, cool and somehow dangerous, I would very conservatively
propose
a proxy method, similiar in spirit to BDD’s rspec#is

%w{the meaning of 42}.map.length => [3, too_long_to_count, 2, 2]

or maybe not overload #map without params for that purpose?

I like this idea too - something that allows you to pick which iterator
method to use (that is: select, each, map)

Implementable syntax might be:

%w{the meaning of 42}.map_length

Where the Array.method_missing could parse it as:

      re = /(map|collect|select|each)_([\\w\\_]+)/
      if (match = method.to_s.match(re))
        puts match.inspect
        iterator, callmethod = match[1..2]
        puts [iterator, callmethod]
        return self.send(iterator) {|item| item.send callmethod}
      end

Cute. I’ve added it to the library.

Ahh. Removed trace and fixed the regular expression:

      re = /(map|collect|select|each|reject)_([\\w\\_]+\\??)/
      if (match = method.to_s.match(re))
        iterator, callmethod = match[1..2]
        return self.send(iterator) {|item| item.send callmethod}
      end

Now supports reject

For example:

list = [nil, 1, 2, nil, 4]
=> [nil, 1, 2, nil, 4]

list.reject_nil?
=> [1, 2, 4]

Cool.

On 9/28/06, [email protected] [email protected] wrote:

=> [86400, 172800, 259200, 345600, 432000, 518400, 604800, 691200,
Actually the reason this is kind of cool is not the syntax but the
I agree, cool and somehow dangerous, I would very conservatively propose
wanted, rather than something that really makes sense when read left
to right.

That’s why I prefer Dr. Nic’s plurals thing, though it definitely has
the disadvantage of entering into murky territority in terms of method
naming.

I like the idea of being able to treat messages like some kind of
object and manipulating stuff with them somewhat like this, so just
yesterday I made a mutated version of Symbol#to_proc, the “first
class” message:

class Message
def initialize(message, *args)
@message, @args = message, args
end

def call(x, *args)
x.send @message, *(@args + args)
end

def to_proc
lambda { |*args| call(*args) }
end
end

if $0 == FILE

Example

puts [*0…100].select(&Message.new(:>, 50)).inspect
puts [*0…100].inject(&Message.new(:+))
puts %w(Once upon a time).map(&Message.new(:upcase)).inspect
puts Message.new(:index, /[aeiou]/, -3).call(“hello”)
puts %w(1 2 3 4
5).map(&Message.new(:to_i)).map(&Message.new(:class)).inspect
end

$ ruby message.rb
[51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
5050
[“ONCE”, “UPON”, “A”, “TIME”]
4
[Fixnum, Fixnum, Fixnum, Fixnum, Fixnum]

The syntax isn’t as terse, but it does have the advantage of not
interfering with existing methods (and you could just write a kernel
method as a shortcut if you like).

Hi –

On Thu, 28 Sep 2006, Dr Nic wrote:

have to make sure you didn’t have two conceptions of some plural
thing.

The method_missing code will perform “super” first to ensure that any
per-existing dynamic methods on the Array are evaluated first. Then it
attempts to call the method on the collection items. If they return a
NoMethodError then the original error object is returned. I think that’s
cleanest.

I’m not sure that’s working the way you think it is. When you call
super in method_missing, it’s calling Object#method_missing, which
really just lives to be overridden. (Or is there some other layer
added in there somewhere?) Also, if a dynamic method of the same name
has been defined on the array, then method_missing won’t get called in
the first place :slight_smile:

David

Simen E. wrote:

The syntax isn’t as terse, but it does have the advantage of not
interfering with existing methods (and you could just write a kernel
method as a shortcut if you like).

Which would look like:

if $0 == FILE

Example

puts [*0…100].select(&message(:>, 50)).inspect
puts [*0…100].inject(&message(:+))
puts %w(Once upon a time).map(&message(:upcase)).inspect
puts message(:index, /[aeiou]/, -3).call(“hello”)
puts %w(1 2 3 4
5).map(&message(:to_i)).map(&message(:class)).inspect
end

Not too shabby. But still I think terseness is the major advantage of
the Symbol#to_proc or the Magic Dot notation (like that name btw). OTOH
this might have other advantages.

T.

unknown wrote:

I’m not sure that’s working the way you think it is. When you call
super in method_missing, it’s calling Object#method_missing, which
really just lives to be overridden. (Or is there some other layer
added in there somewhere?) Also, if a dynamic method of the same name
has been defined on the array, then method_missing won’t get called in
the first place :slight_smile:
David

I’ve got it all bundled in a module, so it could be applied to another
class that did have a meaningful method_missing, but for Array your
point is valid.

By “dynamic method” I mean methods like find_by_<attr_name> in
ActiveRecord - methods that are accepted by the method_missing but don’t
exist, rather than methods that are dynamically added to the class at
some point. Sorry if that’s the wrong name for them.

Having said all that, I don’t think this is working for associations on
ActiveRecords (which are just Arrays). I’m still investigating, but
perhaps rails adds its own method_missing that is negating this one?

Nic

[email protected] wrote:

I personally dislike “magic dot” stuff like that. True, it’s not
really magic, since map can return some kind of enumerator that knows
about the array and is ready to do something to it. But it still has
the look and feel of a kind of secret system for communicating what’s
wanted, rather than something that really makes sense when read left
to right.

Agreed. That is similar to the reason I hate the flip-flop
operator (syntax, not semantics) and even worse, junctions.

Hal

I just could not resist, I am still amazed - after 20 years of Ruby
experience 8-], how easily things can be done, when did I say Ty the
last
time Matz? Probably 20 years ago!!!

Ty Matz!!!

for those who like the notation, very, very lazily tested, but
amazingly
simple, probably rather slow etc…

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

module Enumerable
class MapProxy
def initialize obj
@obj = obj
end #def initialize obj
def method_missing name, *args, &blk
@obj.map do
| ele |
ele.send name, *args, &blk
end
end #def method_missing name, *args, &blk
end #class MapProxy

alias_method :aliased_map, :map
def map *args, &blk
    return MapProxy.new( self ) if args.length.zero? && blk.nil?
    aliased_map *args, &blk
end #def map *args, &blk

end #module Enumerable

---------------- 8< --------------------
Cheers
Robert

Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

Hal F. wrote:

Agreed. That is similar to the reason I hate the flip-flop
operator (syntax, not semantics) and even worse, junctions.

I understand the general feeling you express. But is the reason really
just in the reading? I suspect there’s more to it. What really lies at
the core of this uncomfortable feeling? I think maybe is has more to do
with comprehension of the general pattern. That is to ask, what is this
“magic dot” notation really? Maybe if we understood it better we would
not feel so uncomfortable with it --or at the very least, maybe we
would truly understand why we rightly feel so.

In thinking about it, it seems to me that “magic-dotting” is simply
using a form of Object Adapter Design Pattern. (see
http://en.wikipedia.org/wiki/Adapter_pattern). I think there is a
tendency to over look this and think more in terms of retroactive
application of the subsequent method call. That means were focusing on
the ultimate action, and so it seems odd then b/c we are thinking of
‘.x.y’ in ‘obj.x.y’ as a single message when clearly we see there are
two messages actually being sent. That unsettles us. But as I suggest,
if we see it for what it really is --an adapter, then it makes perfect
sense, and doesn;t seem so magical after all (except that it still
offers some nicely terse syntax).

T.

Trans wrote:

But as I suggest,
if we see it for what it really is --an adapter, then it makes perfect
sense, and doesn;t seem so magical after all (except that it still
offers some nicely terse syntax).

I agree - it is very nice syntax.

Nic

On 9/29/06, Dr Nic [email protected] wrote:


Posted via http://www.ruby-forum.com/.

I’ll have a look at T’s link but it is a truely interesting discussion.
It is for the first time that I hear that very honorable members of the
community dislike this feature.
For me it was the most intreaging of Ruby at all (at the beginning)
I recall my old Python code

something.get_other().do_stuff().transform()

Terrible, it is only now that I realize that the concept of writing code
like this might be flawed, there are OTOH strong indications that it is
not
so flawed too. E.g. lots of programing conventions asking for methods to
return objects.
Now, to complicate things even more, very often that object is “self”,
personally I find the idea to return other objects, especially proxy
objects
to self, very intriguing, the idea came to me when I looked at the BDD
video
cast.

Cheers
Robert


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

Robert D. wrote:

On 9/29/06, Dr Nic [email protected] wrote:


Posted via http://www.ruby-forum.com/.

I’ll have a look at T’s link but it is a truely interesting discussion.

You’re quoting techniques need some practise! I don’t think I said that
:slight_smile:

It is for the first time that I hear that very honorable members of the
community dislike this feature.
For me it was the most intreaging of Ruby at all (at the beginning)
I recall my old Python code

something.get_other().do_stuff().transform()

Terrible, it is only now that I realize that the concept of writing code
like this might be flawed, there are OTOH strong indications that it is
not
so flawed too. E.g. lots of programing conventions asking for methods to
return objects.
Now, to complicate things even more, very often that object is “self”,
personally I find the idea to return other objects, especially proxy
objects
to self, very intriguing, the idea came to me when I looked at the BDD
video cast.

In Smalltalk all methods return self unless they return something else,
thus inferring that something.get_other().do_stuff().transform() (using
Smalltalk syntax :slight_smile: is common.

In JQuery - the javascript library - everything is centered around a
“jquery” object which holds 1+ dom objects in it. All methods on the
jQuery object should return that same set of objects (via the jQuery
object container) so that you can chain method calls.

The idea of returning a proxy object (in the Magic Dot example of
.map.lengths) was very nifty. It works the normal way AND works in the
magical way.

The more and more examples I see and concoct myself like this the more I
am happy to give up the core assumptions of “object oriented
programming” etc, and adopt “happy syntax programming”. Syntax that
reads nicely and makes sense as to what it will do and what it will
return.

Now if I could just get the code I originally wrote to work on
ActiveRecord associations I’d be such a happy camper. I’m in love with
that syntax.

Nic

Dr Nic wrote:

You’re quoting techniques need some practise! I don’t think I said that
:slight_smile:

My ability to spell needs some practise.

“Your” instead of “You’re”… doh!

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs