开发者

Why were ruby loops designed that way?

As is stated in the 开发者_如何学运维title, I was curious to know why Ruby decided to go away from classical for loops and instead use the array.each do ...

I personally find it a little less readable, but that's just my personal opinion. No need to argue about that. On the other hand, I suppose they designed it that way on purpose, there should be a good reason behind.

So, what are the advantages of putting loops that way? What is the "raison d'etre" of this design decision?


This design decision is a perfect example of how Ruby combines the object oriented and functional programming paradigms. It is a very powerful feature that can produce simple readable code.

It helps to understand what is going on. When you run:

array.each do |el|
  #some code
end

you are calling the each method of the array object, which, if you believe the variable name, is an instance of the Array class. You are passing in a block of code to this method (a block is equivalent to a function). The method can then evaluate this block and pass in arguments either by using block.call(args) or yield args. each simply iterates through the array and for each element it calls the block you passed in with that element as the argument.

If each was the only method to use blocks, this wouldn't be that useful but many other methods and you can even create your own. Arrays, for example have a few iterator methods including map, which does the same as each but returns a new array containing the return values of the block and select which returns a new array that only contains the elements of the old array for which the block returns a true value. These sorts of things would be tedious to do using traditional looping methods.

Here's an example of how you can create your own method with a block. Let's create an every method that acts a bit like map but only for every n items in the array.

class Array #extending the built in Array class
  def every n, &block #&block causes the block that is passed in to be stored in the 'block' variable. If no block is passed in, block is set to nil
    i = 0
    arr = []
    while i < self.length
      arr << (   block.nil? ?  self[i] : block.call(self[i])   )#use the plain value if no block is given
      i += n
    end
    arr
  end
end

This code would allow us to run the following:

[1,2,3,4,5,6,7,8].every(2) #= [1,3,5,7]   #called without a block
[1,2,3,4,5,6,7,8,9,10].every(3) {|el| el + 1 } #= [2,5,8,11]   #called with a block

Blocks allow for expressive syntax (often called internal DSLs), for example, the Sinatra web microframework. Sinatra uses methods with blocks to succinctly define http interaction. eg.

get '/account/:account' do |account|
  #code to serve of a page for this account
end

This sort of simplicity would be hard to achieve without Ruby's blocks.

I hope this has allowed you to see how powerful this language feature is.


I think it was mostly because Matz was interested in exploring what a fully object oriented scripting language would look like when he built it; this feature is based heavily on the CLU programming language's iterators.

It has turned out to provide some interesting benefits; a class that provides an each method can 'mix in' the Enumerable module to provide a huge variety of pre-made iteration routines to clients, which reduces the amount of tedious boiler-plate array/list/hash/etc iteration code that must be written. (Ever see java 4 and earlier iterators?)


I think you are kind of biased when you ask that question. Another might ask "why were C for loops designed that way?". Think about it - why would I need to introduce counter variable if I only want to iterate through array's elements? Say, compare these two (both in pseudocode):

for (i = 0; i < len(array); i++) {
  elem = array[i];
  println(elem);
}

and

for (elem in array) {
  println(elem);
}

Why would the first feel more natural than the second, except for historical (almost sociological) reasons?

And Ruby, highly object-oriented as is, takes this even further, making it an array method:

array.each do |elem|
  puts elem
end

By making that decision, Matz just made the language lighter for superfluous syntax construct (foreach loop), delegating its use to ordinary methods and blocks (closures). I appreciate Ruby the most just for this very reason - being really rational and economical with language features, but retaining expressiveness.

I know, I know, we have for in Ruby, but most of the people consider it unneccessary.


The do ... end blocks (or { ... }) form a so-called block (almost a closure, IIRC). Think of a block as an anonymous method, that you can pass as argument to another method. Blocks are used a lot in Ruby, and thus this form of iteration is natural for it: the do ... end block is passed as an argument to the method each. Now you can write various variations to each, for example to iterate in reverse or whatnot.

There's also the syntactic sugar form:

for element in array
   # Do stuff
end

Blocks are also used for example to filter an array:

array = (1..10).to_a
even = array.select do |element|
    element % 2 == 0
end
# "even" now contains [2, 4, 6, 8, 10]


I think it's because it emphasizes the "everything is an object" philosophy behind Ruby: the each method is called on the object.

Then switching to another iterator is much smoother than changing the logic of, for example, a for loop.


Ruby was designed to be expressive, to read as if it was being spoken... Then I think it just evolved from there.


This comes from Smalltalk, that implements control structures as methods, thus reducing the number of keywords and simplifying the parser. Thus allowing controll strucures to serve as proff of concept for the language definition.

In ST, even if conditions are methods, in the fashion:

boolean.ifTrue ->{executeIfBody()}, :else=>-> {executeElseBody()}

In the end, If you ignore your cultural bias, what will be easier to parse for the machine will also be easier to parse by yourself.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜