Can Someone Explain Ruby's Use of Pipe Characters in a Block

Can someone explain Ruby's use of pipe characters in a block?

Braces define an anonymous function, called a block. Tokens between the pipe are the arguments of this block. The number of arguments required depends on how the block is used. Each time the block is evaluated, the method requiring the block will pass a value based on the object calling it.

It's the same as defining a method, only it's not stored beyond the method that accepts a block.

For example:

def my_print(i) 
puts i
end

will do the same as this when executed:

{|i| puts i}

the only difference is the block is defined on the fly and not stored.

Example 2:
The following statements are equivalent

25.times &method(:my_print)

25.times {|i| puts i}

We use anonymous blocks because the majority of functions passed as a block are usually specific to your situation and not worth defining for reuse.

So what happens when a method accepts a block? That depends on the method. Methods that accept a block will call it by passing values from their calling object in a well defined manner. What's returned depends on the method requiring the block.

For example: In 25.times {|i| puts i} .times calls the block once for each value between 0 and the value of its caller, passing the value into the block as the temporary variable i. Times returns the value of the calling object. In this case 25.

Let's look at method that accepts a block with two arguments.

{:key1 => "value1", :key2 => "value2"}.each {|key,value| 
puts "This key is: #{key}. Its value is #{value}"
}

In this case each calls the block ones for each key/value pair passing the key as the first argument and the value as the second argument.

What are those pipe symbols for in Ruby?

They are the variables yielded to the block.

def this_method_takes_a_block
yield(5)
end

this_method_takes_a_block do |num|
puts num
end

Which outputs "5". A more arcane example:

def this_silly_method_too(num)
yield(num + 5)
end

this_silly_method_too(3) do |wtf|
puts wtf + 1
end

The output is "9".

what does the vertical bar character do at the beginning of a method definition's name

'"|" being used at the beginning of a method definition' indeed defines the method |.

Syntax Error in Ruby, Unexpected Pipe Character in a Do

while (x >= 4_000_000)
foo
end

You don't even have to pass in x, because it's accessible in the scope of the enclosing block.

Regex to match pipes not within brackets or braces with nested blocks

The following is a pure-Ruby solution. I assume the braces and brackets in the string are balanced.

str =<<BITTER_END
Some infobox royalty|testing
| name = Louis
| title = Prince Napoléon
| elevation_imperial_note= <ref name="usgs">{{cite web|url={{Gnis3|1802764}}|title=USGS}}</ref>
| a = [[AA|aa]] | b = {{cite
|title=TITLE
|author=AUTHOR}}
BITTER_END

stack = []
last = 0
str.each_char.with_index.with_object([]) do |(c,i),locs|
puts "c=#{c}, i=#{i}, locs=#{locs}, stack=#{stack}"
case c
when ']', '}'
puts " pop #{c} from stack"
stack.pop
when '[', '{'
puts " push #{c} onto stack"
stack << c
when '|'
puts stack.empty? ? " record location of #{c}" : " skip | as stack is non-empty"
locs << i if stack.empty?
end
puts " after: locs=#{locs}, stack=#{stack}"
end.map do |i|
old_last = last
last = i+1
str[old_last..i-1].strip if i > 0
end.tap { |a| a << str[last..-1].strip if last < str.size }
#=> ["Some infobox royalty",
# "testing",
# "name = Louis",
# "title = Prince Napoléon",
# "elevation_imperial_note= <ref name=\"usgs\">
# {{cite web|url={{Gnis3|1802764}}|title=USGS}}</ref>",
# "a = [[AA|aa]]",
# "b = {{cite\n|title=TITLE\n|author=AUTHOR}}"]

Note that, to improve readability, I've broken the string that is the antepenultimate element of the returned array1.

Explanation

For an explanation of how the locations of the pipe symbols on which to split are determined, run the Heredoc above to determine str (the Heredoc needs to be un-indented first), then run the following code. All will be revealed. (The output is long, so focus on changes to the arrays locs and stack.)

stack = []
str.each_char.with_index.with_object([]) do |(c,i),locs|
puts "c=#{c}, i=#{i}, locs=#{locs}, stack=#{stack}"
case c
when ']', '}'
puts " pop #{c} from stack"
stack.pop
when '[', '{'
puts " push #{c} onto stack"
stack << c
when '|'
puts stack.empty? ? " record location of #{c}" : " skip | as stack is non-empty"
locs << i if stack.empty?
end
puts " after: locs=#{locs}, stack=#{stack}"
end
#=> [20, 29, 44, 71, 167, 183]

If desired, one can confirm the braces and brackets are balanced as follows.

def balanced?(str)
h = { '}'=>'{', ']'=>'[' }
stack = []
str.each_char do |c|
case c
when '[', '{'
stack << c
when ']', '}'
stack.last == h[c] ? (stack.pop) : (return false)
end
end
stack.empty?
end

balanced?(str)
#=> true

balanced?("[[{]}]")
#=> false

1 ...and, in the interest of transparency, to have the opportunity to use a certain word.



Related Topics



Leave a reply



Submit