Passing methods around


#1

I’m a Ruby novice working my way through the pickaxe book, and I’ve got
what should be an easy syntax question. On page 84 of chapter 6, the
authors present the following program…

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = Integer(gets)

if times =~ /^t/
  calc = lambda {|n| n*number }
else
  calc = lambda {|n| n+number }
end
puts ((1..10).collect(&calc).join(", "))

…Since everything is an object (right?), I thought I’d try a couple
of different ways to tighten that up. The guts of the first thing I
tried…

puts((1…10).collect{|n| n (times =~ /^t/ ? (*) : (+)) number }.join(",
"))

…results in syntax errors. Tossing in extra dots and ampersands in
several places didn’t cure things for me. So, do I have just a simple
syntax error there, or am I making a fundamental Ruby mistake? I also
tried the following…

puts((1…10)
.(times =~ /^t/ ? collect {|n| n*number } : collect {|n| n+number })
.join(", "))

…which doesn’t work either. Advice?

Thanks,

Greg Buchholz


#2

Haven’t tried it, but the “LHS (some_dynamic_method) RHS” syntax does
seem a bit odd to me. You might try “LHS.send(some_dynamic_method,
RHS)”. So something like:

puts (1…10).collect do |n|
n.send( (times =~ /^t/ ? ‘*’ : ‘+’), number )
end.join(’, ')

Personally, I don’t like it though. You’re making a RegExp match on
every iteration, when you only need the test once, and the Ternary
operation in the middle of all that isn’t the easiest to read.

I’d recommend taking advantage of Ruby’s functional nature instead:

print "(t)imes or §lus: "
times = gets
print "number: "
number = gets.to_i

calc = if times =~ /^t/
lambda { |n| n * number }
else
lambda { |n| n + number }
end

puts (1…10).collect(&calc).join(", ")

Other than that, I wouldn’t worry about making it “simpler”.


#3

Greg Buchholz wrote:

I’m a Ruby novice working my way through the pickaxe book, and I’ve got
what should be an easy syntax question. On page 84 of chapter 6, the
authors present the following program…

[… program elided …]

…Since everything is an object (right?),

Heh! Well, almost. I like to say that everything you can bind to a
variable is an object. The problem is that you are playing games with
syntax. You are trying to represent the operator of an expression with
an expression. Since the operator itself is not a full expression, you
can’t jam an expression in that place. You would get the same error
from (the much simpler):

n (*) number

If you want to dynamically choose the operation, then “send” is your
friend. For example:

n.send("*", number)

will work. Fixing your example to use send would be:

puts((1..10).collect{|n| n.send((times =~ /^t/ ? "*" : "+"), number) 

}.join(","))


– Jim W.


#4

Greg Buchholz wrote:

new functions in C at runtime, but you can do quite a bit with function
pointers). Does that sound about right?

I really like this explanation:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/179372


#5

Jim W. wrote:

Fixing your example to use send would be:

puts((1..10).collect{|n| n.send((times =~ /^t/ ? "*" : "+"), number)

}.join(","))

Just another data point.  It looks like you can also use "method"

and “call”…

puts((1…10).
collect{|n| n.method(times =~ /^t/ ? (:*):(:+)).call(number) }.
join(", "))

Greg Buchholz


#6

Jim W. wrote:

Heh! Well, almost. I like to say that everything you can bind to a
variable is an object. The problem is that you are playing games with
syntax. You are trying to represent the operator of an expression with
an expression. Since the operator itself is not a full expression, you
can’t jam an expression in that place.

OK. I’m trying to get aquinted with the terminology. Would we say
that operators, methods, and messages aren’t first class entities?
They’re second class citizens, like functions in C (you can’t create
new functions in C at runtime, but you can do quite a bit with function
pointers). Does that sound about right?

puts((1…10).collect{|n| n.send((times =~ /^t/ ? “*” : “+”), number) }.join(","))

Works like a charm.

Thanks,

Greg Buchholz


#7

Greg Buchholz wrote:

OK. I’m trying to get aquinted with the terminology. Would we say
that operators, methods, and messages aren’t first class entities?
They’re second class citizens, like functions in C (you can’t create
new functions in C at runtime, but you can do quite a bit with function
pointers). Does that sound about right?

Nah. You’re confusing syntax with identity. The token “*” there is not
itself a method – you can’t assign it to a variable or anything. It
just tells Ruby to call a method. But you can dynamically generate
methods, and you can store a method in a variable – just not with that
syntax.

Actually, Ruby is the opposite of C as far as what you can do at
runtime. Ruby is all about runtime. For instance, when you’re defining
a Person class and you write “attr_accessor :shoe_size”, you’re calling
a method that dynamically creates (at runtime) the methods “shoe_size”
and “shoe_size=” and defines them to get and set (respectively) the
instance variable @shoe_size. Some people have made classes that
actually define methods as they’re called.


#8

“Would we say that operators, methods, and messages aren’t first class entities?”

No, the deal is that ‘+’, ‘*’, etc aren’t some special “operator” type
in Ruby, they’re just methods. The parser is what makes them look like
traditional operators. So “x + y” is calling the ‘+’ method on ‘x’, and
passing ‘y’ as a parameter.

So in saying “x some_daynamic_stuff y” you’re probably thinking “that
should come out looking like ‘x + y’, so it should work.”.

The problem, is that to ruby it looks like: “Object1 some_method
Object2”

When you use symbols such as +, -, *, /, etc, the parser just pretends
you have a ‘.’ there, so to you ‘x + y’, but to Ruby, ‘x.+(y)’.

Except the parser can’t make this magic the way you’re trying, because
you’re trying to execute it to see a result; something that happens
after parsing. So to ruby your method looks malformed. It’s easier to
see if instead of a ‘+’ or ‘*’ method, we use something else: ‘Greg’
to_i 16

See, ‘Greg’ isn’t the reciever for ‘to_i’. There’s no ‘.’ to tell it
so. This is what you’re ending up with. The ‘send()’ syntax mentioned
earlier is a way to specify the reciever.

I would definitely consider methods “first-class” citizens in Ruby. The
trick is that parenthesis in Ruby are optional. So unlike some
JavaScript such as:

var meth = “Greg”.toString;

Which would assign the value of the toString member of “Greg” to a
variable called “meth”, in Ruby this would actually execute the
“toString” method. Because parens are optional. So to refer to a method
without executing it, you’d use:

meth = ‘Greg’.method(:to_s)

Which would return a BoundMethod, which has methods and attributes of
it’s own. So they’re definitely objects. It’s just that getting a
handle on the object is made a little difficult since accessing the
member directly executes it, parens or no parens.

So you have a choice: Always force the use of parens for method calls,
or have a helper for reflection so parens can be optional. Ruby chose
the latter, (and I definitely think it was the right choice).

Sorry for going on so long…