It’s not about rebinding the block or anything. It’s about syntax sugar. Essentially what Brian wrote in the first post. This:
App.routes& do
get :foo
post :bar
end
Translates to this:
App.routes do |c|
c.get :foo
c.post :bar
end
There’s no more than that. If we go that route there’s no need to have with ... yield
, you can just do yield some_object
. In fact it could work even if the author that wrote the method didn’t think of it being used like that.
For example:
('a'..'z').each& { ::puts upcase } # print 'A', 'B', etc.
That will work, because the above is expanded to:
('a'..'z').each { |c| ::puts c.upcase }
The way it works is it just rewrites all methods that don’t have a receiver to method that have an a receiver that’s the first argument yielded to the block.
(I had to use ::puts
instead of puts
to mention the top-level because otherwise it will be rewritten as c.puts
, which is not what I want)
In fact, if we go that route you could even capture those blocks to invoke them later:
class Foo
def initialize(@target : String)
end
def foo
puts "Hello #{@target}!"
end
end
def callback_on_foo(&block : Foo ->)
block
end
block = callback_on_foo& do |x|
foo
end
block.call(Foo.new("world"))
I like that &
is used for this, because in the previous example you have:
callback_on_foo& do |x|
foo
end
which can also be written like this:
callback_on_foo &.foo
However, we can’t use the call& do
syntax because &
is a binary operator (call & exp
) and we’ll need a look ahead to check if do
or {
comes after &
. Maybe it’s fine, but I generally don’t like look aheads when parsing.
Also, what if the method has arguments, how do you write it?
call(1, 2)& do
end
call&(1, 2) do
end
It’s a bit strange. I do like &
but maybe it should be near the block, because it’s essentially changing the block. So maybe:
call(1, 2) do&
something
end
call(1, 2) {& something }
Or maybe even:
call(1, 2) do &.
something
something_else
end
call(1, 2) { &. something }
# With a real example
App.routes do &.
get :foo
post :bar
end
You put &.
after do
or {
and it means “calls that don’t have a receiver now take the first argument as a receiver inside the block”… which is what we are using &.
for right now.
And putting &
after do
or {
in the beginning of a block gives a parsing error right now, so that syntax is available and it can be implemented without a look ahead.
Finally, doing it this way will simplify the compiler’s code, which is always good because the simpler it is, the easier it is to understand it and to evolve it.