Ted Leung on the air
Ted Leung on the air: Open Source, Java, Python, and ...
Ted Leung on the air: Open Source, Java, Python, and ...
Tue, 15 Nov 2005
A wonderful hack?
[23:33] |
[computers/programming] |
# |
TB |
F |
G |
8 Comments |
This fall in our Bainbridge Island reading group, we are going through the Ruby on Rails and Ruby (Pickaxe) book. At our last meeting, one of the things that we discussed was Ruby closures, and I was trying to help people understand what was going on. Turns out there was one area where I didn't quite understand what was going on: the ability to pass an existing function as a block argument. I thought that you'd be able to do that, but apparently you can't.
A few days afterwards I finally got around to reading this post by Dave Thomas on the Symbol#to_proc hack, which I've excerpted:
The Ruby Extensions Project contains an absolutely wonderful hack. Say you want to convert an array of strings to uppercase. You could write
result = names.map {|name| name.upcase}
Fairly concise, right? Return a new array where each element is the corresponding element in the original, converted to uppercase. But if you include the Symbol extension from the Ruby Extensions Project, you could instead write
result = names.map(&:upcase)
Now that’s concise: apply the upcase method to each element of names.
Ouch. My idea of concise would have been:
result = names.map(upcase)
Or am I missing something here?
In Ruby methods are not first-class objects as e.g. in Python. So the name "upcase" is unknown and you have to use a symbol if you don't want to do deep magic hacking inside the Ruby interpreter. So the syntax is "concise" in a Ruby world.
Posted by Nils Kassube at Wed Nov 16 03:07:59 2005
Posted by Nils Kassube at Wed Nov 16 03:07:59 2005
Here's a little snippet to help you understand why:
(mapcar #'upcase names)
Combine that with what Nils said.
Posted by Vincent Foley at Wed Nov 16 03:10:58 2005
(mapcar #'upcase names)
Combine that with what Nils said.
Posted by Vincent Foley at Wed Nov 16 03:10:58 2005
IMHO, this is the single biggest syntax difference between Ruby and Python. Methods really can't be first-class objects without destroying Ruby's accessor syntax (a.filename="/tmp/foo") and paren-less calling syntax (which is critical for domain-specific language use).
On the other hand, Procs are first-class objects, and it's easy to convert a method to a Proc (and vice-versa).
Personally, I think 'result = names.map(:upcase)' would be nice to see in the language--it's the closest to LISP, and the most concise way to say this while actually being valid Ruby code.
Posted by Scott Laird at Wed Nov 16 07:26:38 2005
On the other hand, Procs are first-class objects, and it's easy to convert a method to a Proc (and vice-versa).
Personally, I think 'result = names.map(:upcase)' would be nice to see in the language--it's the closest to LISP, and the most concise way to say this while actually being valid Ruby code.
Posted by Scott Laird at Wed Nov 16 07:26:38 2005
result = names.map(:upcase) would indeed be better, but it would require (I assume) changes to the map method (in Ruby source), unless it can be replaced with a new version in an extension library.
I wouldn't call the usage of names.map(&:upcase) "wonderful" either... it's a rather gross abuse of the & operator. This only strengthens my opinion that the language is rather messy (1, 2). :-)
Posted by Hans Nowak at Wed Nov 16 09:49:26 2005
I wouldn't call the usage of names.map(&:upcase) "wonderful" either... it's a rather gross abuse of the & operator. This only strengthens my opinion that the language is rather messy (1, 2). :-)
Posted by Hans Nowak at Wed Nov 16 09:49:26 2005
Slightly different explanation ;-)
upcase there would send the upcase message. Methods are more like smalltalk methods where they are message handlers, so what you need to do is create a proc from something that will send a message when invoked to the correct place.
Python functions are functions, not messages being sent to a receiver (well yes, but not quite as literally as ruby) so foo gets you the function and () says to invoke it. In ruby parens are order of operation helpers, they can be used when it is ambiguous, or you want different than normal binding.
an alternative is method(:upcase) which returns a proc which will send :upcase to the current default receiver (self) where the call is invoked (the clsosure).
=)
Posted by Brian McCallister at Wed Nov 16 12:16:26 2005
upcase there would send the upcase message. Methods are more like smalltalk methods where they are message handlers, so what you need to do is create a proc from something that will send a message when invoked to the correct place.
Python functions are functions, not messages being sent to a receiver (well yes, but not quite as literally as ruby) so foo gets you the function and () says to invoke it. In ruby parens are order of operation helpers, they can be used when it is ambiguous, or you want different than normal binding.
an alternative is method(:upcase) which returns a proc which will send :upcase to the current default receiver (self) where the call is invoked (the clsosure).
=)
Posted by Brian McCallister at Wed Nov 16 12:16:26 2005
ps: & is basically short hand for lambda, I believe, though I am not sure if it binds more or less tightly than lambda. This is belief on my part based on behavior, not having poked at the implentation to any great extent.
Posted by Brian McCallister at Wed Nov 16 12:19:50 2005
Posted by Brian McCallister at Wed Nov 16 12:19:50 2005
Thanks everybody for the followups!
Scott, thanks for the commentary on paren-less calling and DSL's. I hadn't made that connection yet (I'm not finished with the books). That helps a lot actually.
Posted by Ted Leung at Wed Nov 16 23:10:21 2005
Scott, thanks for the commentary on paren-less calling and DSL's. I hadn't made that connection yet (I'm not finished with the books). That helps a lot actually.
Posted by Ted Leung at Wed Nov 16 23:10:21 2005
I still think most of the discussed alternatives are better than my pidgin JavaScript version:
WRT the impossibility of extracting method objects from Ruby objects because the foo.bar syntax already has a meaning: there are several languages (Lisp, FORTH, PostScript) in which you can use some contextual marker (', ', and / respectively) to get the name of something rather than invoking it. Perhaps in Unicode we should use a pentagram for this function. One could certainly alter Ruby's syntax in this way, at least in theory.
Posted by Kragen Sitaker at Wed Nov 30 19:15:14 2005
map(compose(td, method("as_html")), this.prefs)
WRT the impossibility of extracting method objects from Ruby objects because the foo.bar syntax already has a meaning: there are several languages (Lisp, FORTH, PostScript) in which you can use some contextual marker (', ', and / respectively) to get the name of something rather than invoking it. Perhaps in Unicode we should use a pentagram for this function. One could certainly alter Ruby's syntax in this way, at least in theory.
Posted by Kragen Sitaker at Wed Nov 30 19:15:14 2005
You can subscribe to an RSS feed of the comments for this blog:
Add a comment here:
You can use some HTML tags in the comment text:
To insert a URI, just type it -- no need to write an anchor tag.
Allowable html tags are:
You can also use some Wiki style:
URI => [uri title]
<em> => _emphasized text_
<b> => *bold text*
Ordered list => consecutive lines starting spaces and an asterisk
To insert a URI, just type it -- no need to write an anchor tag.
Allowable html tags are:
<a href>
, <em>
, <i>
, <b>
, <blockquote>
, <br/>
, <p>
, <code>
, <pre>
, <cite>
, <sub>
and <sup>
.You can also use some Wiki style:
URI => [uri title]
<em> => _emphasized text_
<b> => *bold text*
Ordered list => consecutive lines starting spaces and an asterisk