New magical version of Symbol.to_proc

D

Dr Nic

[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
=> [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:
=> [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
 
D

Dr 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.
 
D

dblack

Hi --

=> [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 :)
(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!

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

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
D

Dr Nic

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"!
 
D

dblack

Hi --

(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!

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.


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

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
D

Dr Nic

Robert said:
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.
 
D

Dr Nic

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.
 
B

Brad Phelan

Nice trick!

--

class Symbol
def to_proc
Proc.new { |obj, *args| obj.send(self, *args) }
end
end

puts ["foo", "bar", "cat", "dog"].collect &:reverse!
oof
rab
tac
god


##

But why does this not work?

a = &:foo

sym.rb:9: parse error, unexpected tAMPER
a = &:reverse!

( Using ruby ruby 1.8.4 (2005-12-24) [i486-linux] )
 
S

Simen Edvardsen

Hi --

(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!

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.


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 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.

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).
David

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
D

dblack

Hi --

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


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 :)


David

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
T

Trans

Simen said:
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.
 
D

Dr Nic

unknown said:
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 :)
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
 
H

Hal Fulton

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
 
T

Trans

Hal said:
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.
 
D

Dr Nic

Trans said:
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
 
D

Dr Nic

You're quoting techniques need some practise! I don't think I said that
:)
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 :) 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
 
D

dblack

Hi --

I agree - it is very nice syntax.

The ship has clearly sailed on the magic dot, but for the record let
me explain what I don't like about it.

My problem with something like this:

x.should.be.equal.to(y)

(and I don't mean to parody RSpec; that's a made-up example but I
think it's pretty close) is that it uses method calling *syntax* for
method and/or argument name *semantics*. To my eye it's basically
just clustering a bunch of words together that pertain to what's going
on, and connecting them with dots in a kind of imitation of spaces or
underscores.

It's very hard to know what "x.should.be" returns. I can figure it
out, of course. But it has a tone of "don't worry about what's
actually happening; just read the dot-connected words like a string",
which makes me uncomfortable.

(Actually I think RSpec itself now allows underscores to formulate
method names.)

I do understand how the "magic dot" works. It works just like every
other dot :) I just think there are better-fitted ways to do most of
these things.

array.lengths, for example :)

Anyway, just to save some bandwidth:

* I really do understand how it works :)
* I do know that all method chaining has a bit of this quality.
* I'm not trying or expecting to stop anyone from doing it; I'm
just discussing the possible drawbacks.


David

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
D

dblack

Hi --

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.

I don't think anyone objects to method-chaining in every case. My
dislike is for method chaining that, to my eye and brain, is just a
way of composing a longer method name (or something that could be done
by passing in arguments representing conditions) with dots.

Your.mileage.may.vary :)


David

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
D

Dr Nic

My problem with something like this:
x.should.be.equal.to(y)

It's very hard to know what "x.should.be" returns. I can figure it
out, of course. But it has a tone of "don't worry about what's
actually happening; just read the dot-connected words like a string",
which makes me uncomfortable.

This would be readable assuming that "should" on x meant something. If I
saw this I'd guess its like "assert".

But, assuming that your list of words DID mean something, then it has to
be more readable to newcomers (and yourself in 12 mths time) than the
possible equivalent:

x.each {|an_x| assert_equal y, x}

or to rephrase the example, perhaps:

x.should {|an_x| an_x.be :equal, :to, y}

The magic dot gives you left-to-right readability, whereas the above
makes you figure out whats happening in the closure first, then go back
to the x.each part to see where an_x comes from.

Still, you'd have to create an appropriate proxy class for each message
you want to support. That could be a pain.

All good fun though :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top