Passing blocks is a much bigger deal to me in terms of expressiveness. Being able to pass around blocks lets you write APIs that are as powerful as language features. The new Routes API in Rails 3 is a great example of this:
resources :people do
resource :avatar
member do
get :republish_deep_links
post :resend
put :accessible_projects, :upload_preference
end
end
That code sets up various URL paths in a Rails app. It maps URLs like /people/george/resend to the proper controller in the MVC framework.
In many other languages, you could set up an API like this, but instead of taking a block, the "resources" function would have to accept some sort of nested data structure. That gets you pretty far, but what about when you want to do something that the parser of that data structure didn't expect? Usually you can't.
But in Ruby, code is data and you can do whatever you want in those blocks. You could do this, for example:
resources :people do
resource :avatar
member do
["apple", "orange", "peach"].each do |fruit|
get "eat_" + fruit
end
end
end
That would map /people/george/eat_apple, /people/george/eat_orange, and /people/george/eat_peach.
And maybe that's not that impressive to you, but you can inject arbitrary code in there. That's what people mean when they talk about expressiveness.
Examples of this are everywhere in the Ruby world. Check out HAML and assert_select.
APIs + Rigid data structures = Pain and suffering
APIs + Ruby blocks = Flexibility and power