Using a block to surround a string?

I

Ivan Vega

Hi,

I'm trying something very simple, like passing a method a string, and
two more strings to surround that with... i.e.:

around_string('mystring') {|b, a| b = 'before'; a = 'after';}

The method:

def around_string(string, &block)
b, a = yield
"#{b}#{string}#{a}"
end

That of course doesn't work because I still don't get how blocks and
procs work and what they are. The reason I don't just pass 'a' and 'b'
as parameters to the method is because the method has other parameters I
don't want to touch, and it seemed like a block as the last parameter
would be a good choice (I simplified my example so it's clearer).

So I wanted to know what's the best way to accomplish what I'm trying to
do...

Thanks for your patience :)

- Ivan V.
 
S

SonOfLilit

Read in the pickaxe about passing a hash of parameters to a method
(like Rails does a lot)


Aur
 
G

Gregory Brown

Hi,

I'm trying something very simple, like passing a method a string, and
two more strings to surround that with... i.e.:

around_string('mystring') {|b, a| b = 'before'; a = 'after';}

The method:

def around_string(string, &block)
b, a = yield
"#{b}#{string}#{a}"
end

That of course doesn't work because I still don't get how blocks and
procs work and what they are. The reason I don't just pass 'a' and 'b'
as parameters to the method is because the method has other parameters I
don't want to touch, and it seemed like a block as the last parameter
would be a good choice (I simplified my example so it's clearer).

SonOfLilit makes the best suggestion, you can just use a hash as your
last parameter

def around_string(string,other,args,options={})
# whatever
"#{options[:left]}#{string}#{options[:right]}"
end

around_string "foo", ..., :left => "$", :right => "!"

But here's some background on blocks.

def foo
a = yield # a gets the return value of the block
a + 1
end

foo { 4 } #=> 5

def foo
a = yield(3,5) # a passes 3 and 5 to the block
a + 1
end

foo { |b,c| b * c } #=> 16

So you see, you need to pass values to yield for them to be passed
into the block, and the result of the yield is just whatever the block
evaluates to.

Because it's a closure you can also use things in the local scope.

d = 5
foo { |b,c| b * c + d } #=> 21

So if you *really* wanted to use blocks in your original example, you could do:

def around_string(string, &block)
b, a = yield
"#{b}#{string}#{a}"
end

around_string('mystring') { ['before','after'] }

But that is ugly and unidiomatic. :)

Hope this helps,
-greg
 
D

dblack

Hi --

Hi,

I'm trying something very simple, like passing a method a string, and
two more strings to surround that with... i.e.:

around_string('mystring') {|b, a| b = 'before'; a = 'after';}

The method:

def around_string(string, &block)
b, a = yield
"#{b}#{string}#{a}"
end

That of course doesn't work because I still don't get how blocks and
procs work and what they are. The reason I don't just pass 'a' and 'b'
as parameters to the method is because the method has other parameters I
don't want to touch, and it seemed like a block as the last parameter
would be a good choice (I simplified my example so it's clearer).

So I wanted to know what's the best way to accomplish what I'm trying to
do...

Thanks for your patience :)

The way a code block works, basically, is that it hangs off the right
of the method call (as you've got it); it contains code; and the
method has the ability to execute that code, using yield. The value
returned from the block, using standard Ruby statement/expression
evaluation, is the value of the yield statement itself.

Thus:

def my_method
x = yield
puts "I got back #{x}"
end

my_method { 10 } # I got back 10
my_method { "a string" } # I got back a string

In your case, you would want:

def around_string(string)
b,a = yield
"#{b}#{string}#{a}"
end

around_string("abc") { ["before", "after"] } # beforeabcafter

Note that all the block has to do is provide a value. You *can* pass
arguments to a block -- but in this case there's not much point, since
all that the method actually needs is two strings (which I've packed
up in an array).

Also, note that you don't need &block. &block is a special parameter
syntax that takes the code block and turns it into a Proc object. You
need that if you're planning to pass the block around as an object, or
call another method using the same block; but if all you need to do is
yield, you can just do it.


David

P.S. You could also write your method as:

def around_string(string)
yield.join(string)
end

but that's just for fun :)

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
P

Pete Yandell

Ivan said:
around_string('mystring') {|b, a| b = 'before'; a = 'after';}

You want:

around_string('mystring') { ['before', 'after'] }
def around_string(string, &block)
b, a = yield
"#{b}#{string}#{a}"
end

And you wnat:

def around_string(string)
b, a = yield
"#{b}#{string}#{a}"
end

For a quick intro to blocks, see the five minute talk I gave on them
last week:

http://www.cogentconsulting.com.au/screencasts/index.html

A block, however, seems like a terrible way of doing this. Have you
considered using an options hash?

around_string('mystring', :before => 'before', :after => 'after')

def around_string(string, *options)
"#{options[:before]}#{string}#{options[:after]}"
end


Pete Yandell
http://notahat.com/
 
D

dblack

Hi --

So if you *really* wanted to use blocks in your original example, you
could do:

def around_string(string, &block)
b, a = yield
"#{b}#{string}#{a}"
end

around_string('mystring') { ['before','after'] }

But that is ugly and unidiomatic. :)



Is the &block parameter necessary? When is it necessary?

Only if you need to capture the block in an object.
And so I take it with blocks, they are given a new reference to the objects
in question and therefore assigning to the variables in the block has no
effect on the method. ie

def around_string(string, &block)
b = nil
a = nil
yield(b,a)
"#{b}#{string}#{a}"
end

around_string('center') { |b, a| b = 'left'; a = 'right' }

is worthless because in the block I'm merely assigning objects to local
references that go out of scope once the block exits?

Method definitions always have their own local scope, so a, b, and
string are strictly local to the method. Blocks pick up the scope
that they're created in, and can also create variables that weren't
already in that scope. Those variables disappear when the block
exits; the ones that were there already survive:

x = 1
some_method { y = x + 1 } # same x; new y
y # undefined; block's y didn't survive
x # same x


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
G

Gregory Brown

Is the &block parameter necessary? When is it necessary?

You should not use &block and yield together. Use one or the other.

&block is necessary when you are passing a block to another method.

e.g.

def foo(&a)
puts "In foo"
bar(&a)
end

def bar
puts "In bar"
yield
end

foo { puts "in block" }

OUTPUT:

In foo
In bar
in block
And so I take it with blocks, they are given a new reference to the objects
in question and therefore assigning to the variables in the block has no
effect on the method. ie
def around_string(string, &block)
b = nil
a = nil
yield(b,a)
"#{b}#{string}#{a}"
end

around_string('center') { |b, a| b = 'left'; a = 'right' }
is worthless because in the block I'm merely assigning objects to local
references that go out of scope once the block exits?

Right, block local variables that are defined within the block
disappear when the block exits. Blocks can access and modify local
variables in the scope the *block* is defined, but not within the
method.

Here's a set of examples all rolled together for that:

def foo
a = 1
yield(a)
puts "a in method #{a}"
end

b = 3

puts "b before block: #{b}"

foo { |a| b += 2; a += 1; puts "a in block: #{a}" }

OUTPUTS:

b before block: 3
a in block: 2
a in method 1
b after block: 5
puts "b after block: #{b}"
 
I

Ivan V.

Everyone said:
Very useful replies!

I've learned plenty from all your responses, and based on your input I'm
changing the way my code works.

Thanks a lot!

- Ivan V.
 

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,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top