Ruby中的代码块如何知道什么变量属于对象的某个方面?

时间:2018-06-26 17:21:04

标签: ruby

请考虑以下内容:

(1..10).inject{|memo, n| memo + n}

问题:

n如何知道应该存储1..10中的所有值?我很困惑Ruby如何理解n可以立即与(1..10)自动关联,而备忘录只是备忘录。

我知道Ruby代码块与C或Java代码块不同-Ruby代码块的工作方式略有不同。我对直立管道“ |”之间的变量如何感到困惑将自动分配给对象的各个部分。例如:

hash1 = {"a" => 111, "b" => 222}
hash2 = {"b" => 333, "c" => 444}
hash1.merge(hash2) {|key, old, new| old}

“ |键,旧,新|”如何操作自动分配自己,这样当我在代码块中键入“ old”时,是否自动意识到“ old”是指较旧的哈希值?我从来没有给任何东西分配“旧的”,只是声明了它。有人可以解释它的工作原理吗?

4 个答案:

答案 0 :(得分:2)

该块的参数由方法定义确定。 reduce/inject的定义已重载(docs)并在C中进行了定义,但是如果要定义它,则可以这样进行(请注意,这并不涵盖了所有重载的情况)实际的reduce定义):

module Enumerable
  def my_reduce(memo=nil, &blk)
    # if a starting memo is not given, it defaults to the first element
    # in the list and that element is skipped for iteration
    elements = memo ? self : self[1..-1]
    memo ||= self[0]
    elements.each { |element| memo = blk.call(memo, element) }
    memo
  end
end

此方法定义确定用于memoelement的值,并以特定顺序调用blk变量(传递给该方法的块)。

但是请注意,这些块与常规方法不同,因为它们不检查参数的数量。例如:(请注意,此示例显示了yield的用法,这是传递块参数的另一种方式)

def foo
  yield 1
end

# The b and c variables here will be nil
foo { |a, b, c| [a,b,c].compact.sum } # => 1

您也可以在运行块时使用解构来定义变量,例如,如果您想reduce遍历哈希,则可以执行以下操作:

# this just copies the hash
{a: 1}.reduce({}) { |memo, (key, val)| memo[key] = val; memo }

这是如何工作的,在哈希上调用reduce会隐式调用to_a,它将其转换为元组列表(例如{a: 1}.to_a = [[:a, 1]])。 reduce将每个元组作为第二个参数传递给该块。在调用块的地方,元组被分解为单独的键和值变量。

答案 1 :(得分:2)

代码块只是一个没有名称的函数。像其他任何函数一样,可以使用不同的参数多次调用它。如果您有方法

def add(a, b)
  a + b
end

add如何知道有时a5而有时a7

Enumerable#inject只需为每个元素调用一次函数,并将元素作为参数传递。

它看起来像这样:

module Enumerable
  def inject(memo)
    each do |el|
      memo = yield memo, el
    end
    memo
  end
end

答案 2 :(得分:1)

  

备忘录只是备忘录

“只是备忘录”是什么意思? $check = mysql_query("SELECT * FROM `samp_ucp` WHERE `IP` = '$ip'"); if(mysql_num_rows($check)) return true; else return false; memo接受n传递的任何值。并且实现了将累加器/内存作为第一个参数,将当前收集元素作为第二个参数。

  

“ |键,旧,新|”如何操作自动分配自己

他们不“分配自己”。 inject为其分配。或者更确切地说,按顺序将这些值(键,旧值,新值)作为块参数传递。

如果您改写

merge

它仍然可以像以前一样完全运行。参数名称什么也没有[此处]。重要的是实际值。

答案 3 :(得分:1)

只是在这里简化一些其他好的答案:

如果您在努力理解块,那么将其视为一种简单的方法是将其作为原位和临时方法创建并执行,并且管道字符|memo|之间的值只是参数签名。

参数后面没有特殊的特殊概念,它们只是您要调用的方法将变量传递给的方法,就像使用参数调用其他方法一样。与方法类似,参数是块范围内的“局部”变量(根据您用于调用该块的语法的不同,这会有一些细微差别,但我离题,这是另一回事)。

您将传递给该块的方法仅调用此“临时方法”,并将其设计要执行的参数传递给它。就像正常调用方法一样,它们之间有一些细微差别,例如没有“必需”参数。如果您未定义任何要接收的参数,则很高兴只是不传递参数而不是引发ArgumentError。同样,如果您为要接收的块定义了太多的参数,则它们将简单地位于块内的nil中,没有被定义的错误。