正如标题中所述,我很想知道为什么Ruby决定放弃经典的for循环,而是使用array.each do ...
我个人认为它的可读性稍差,但这只是我个人的意见。无需争论。另一方面,我认为他们是故意设计的,应该有充分的理由。
那么,以这种方式放置循环有什么好处?这个设计决定的“存在理由”是什么?
答案 0 :(得分:7)
这个设计决策是Ruby如何结合面向对象和函数式编程范例的完美例子。这是一个非常强大的功能,可以生成简单易读的代码。
有助于了解正在发生的事情。当你跑:
array.each do |el|
#some code
end
您正在调用each
对象的array
方法,如果您认为该变量名称,则该方法是Array
类的实例。您正在将代码块传递给此方法(块相当于一个函数)。然后,该方法可以评估此块并使用block.call(args)
或yield args
传递参数。 each
只是遍历数组,对于每个元素,它调用您传入的块作为参数。
如果each
是使用块的唯一方法,那么这不是那么有用,而是许多其他方法,您甚至可以创建自己的方法。例如,数组有一些迭代器方法,包括map
,它们每个都做同样但返回一个包含块的返回值的新数组,select
返回一个只包含元素的新数组块返回true值的旧数组。使用传统的循环方法做这些事情会很繁琐。
以下是如何使用块创建自己的方法的示例。让我们创建一个every
方法,它有点像地图,但只适用于数组中的每个n项。
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
此代码允许我们运行以下内容:
[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
块允许表达语法(通常称为内部DSL),例如,Sinatra web微框架。 Sinatra使用块的方法来简洁地定义http交互。 例如
get '/account/:account' do |account|
#code to serve of a page for this account
end
如果没有Ruby的块,这种简单性很难实现。
我希望这能让你看到这种语言功能有多强大。
答案 1 :(得分:2)
我认为这主要是因为Matz有兴趣探索一种完全面向对象的脚本语言,当他构建它时会是什么样子;此功能主要基于CLU编程语言的迭代器。
结果证明它提供了一些有趣的好处;提供each
方法的类可以“混合”Enumerable
模块,为客户端提供各种预先制作的迭代例程,从而减少繁琐的样板阵列/列表/必须编写的hash / etc迭代代码。 (见过java 4 and earlier iterators?)
答案 2 :(得分:1)
当你提出这个问题时,我认为你有点偏颇。另一个人可能会问“为什么C for
循环是这样设计的?”。想一想 - 如果我只想迭代数组的元素,为什么我需要引入计数器变量?比方说,比较这两个(都是伪代码):
for (i = 0; i < len(array); i++) {
elem = array[i];
println(elem);
}
和
for (elem in array) {
println(elem);
}
为什么第一种感觉比第二种更自然,除了历史(几乎是社会学)的原因?
Ruby,高度面向对象,更进一步,使它成为一个数组方法:
array.each do |elem|
puts elem
end
通过做出这个决定,Matz只是为多余的语法构造(foreach循环)使语言更轻,将其用途委托给普通的方法和块(闭包)。我最感谢Ruby就是出于这个原因 - 在语言特征方面真正理性和经济,但保留了表现力。
我知道,我知道,我们在Ruby中有for
,但大多数人都认为这是不必要的。
答案 3 :(得分:0)
Ruby被设计为具有表现力,阅读就像是在说话......然后我认为它只是从那里发展而来。
答案 4 :(得分:0)
do ... end
块(或{ ... }
)形成一个所谓的块(几乎是一个闭包,IIRC)。可以将块视为匿名方法,您可以将其作为参数传递给另一个方法。块在Ruby中经常使用,因此这种迭代形式很自然:do ... end
块作为参数传递给方法each
。现在,您可以为each
编写各种变体,例如以反向或其他方式进行迭代。
还有语法糖形式:
for element in array
# Do stuff
end
块也用于过滤数组:
array = (1..10).to_a
even = array.select do |element|
element % 2 == 0
end
# "even" now contains [2, 4, 6, 8, 10]
答案 5 :(得分:0)
我认为这是因为它强调Ruby背后的“一切都是对象”的理念:在对象上调用each
方法。
然后切换到另一个迭代器比更改例如for
循环的逻辑要平滑得多。
答案 6 :(得分:0)
这来自Smalltalk,它将控制结构实现为方法,从而减少了关键字的数量并简化了解析器。因此,允许控制结构充当语言定义的概念。
在ST中,即使条件是方法,也是如此:
boolean.ifTrue ->{executeIfBody()}, :else=>-> {executeElseBody()}
最后,如果你忽略了你的文化偏见,那么更容易解析机器也会更容易自己解析。