为什么我不能在Ruby代码块中重新分配变量?

时间:2016-09-08 07:37:29

标签: ruby variables methods block

为什么不调用这两个.map方法会产生相同的结果?第一个按预期工作,而第二个没有效果。

array = ["batman","boobytrap"]

puts array.map { |x| x.reverse! }
=> namtab
=> partyboob

puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap

5 个答案:

答案 0 :(得分:1)

问题在于,在你的第一张地图中!已修改原始数组中的值,因此它现在包含反向字符串。

irb:001:0> array = ["batman","boobytrap"]
=> ["batman", "boobytrap"]
irb:002:0> puts array.map { |x| x.reverse! }
namtab
partyboob
=> nil
irb:003:0> array
=> ["namtab", "partyboob"]

所以第二次正在做你期望的事情,但是输入数据不是你想象的那样。 如果您在没有完成第一个案例的情况下单独试用第二个案例,您将看到它按预期工作。

答案 1 :(得分:1)

array之后打印puts array.map { |x| x.reverse! }。你会看到 - 数组已经改变。 阅读reverse!方法的documentation

答案 2 :(得分:1)

你必须改变你对变量的看法。变量不是实际值,而只是对该值的引用。

array = ["batman"]

# We are freezing this string to prevent ruby from creating a
# new object for a string with the same value.
# This isnt really necessary, just to "make sure" the memory
# address stays the same.
string = "boobytrap".freeze

array.each do |val|
  puts "before: ",
       "val.object_id: #{val.object_id}",
       "string.object_id: #{string.object_id}",
       "array[0].object_id: #{array[0].object_id}",
       ""

  val = string

  puts "after: ",
       "val.object_id: #{val.object_id}",
       "string.object_id: #{string.object_id}",
       "array[0].object_id: #{array[0].object_id}"
end

# before:
# val.object_id: 70244773377800,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800
#
# after:
# val.object_id: 70244761504360,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800

显然,如果在您的机器上运行此代码,则值会有所不同,但关键是,val的内存地址会发生变化,而array[0](val来自的地方)保持不变在我们将字符串分配给val之后。基本上我们对重新分配的处理方式是,我们告诉ruby,在70244773377800中找不到val的值,但是在70244761504360中。尽管数组仍然引用它的第一个值70244773377800!

另一方面,您在x上的示例中使用的#reverse!方法调用会更改内存中70244773377800处找到的任何值,这就是它按预期工作的原因。

TLDR; 您的第一个示例更改了内存中的值,而第二个示例为本地变量分配了一个新的内存地址。

答案 3 :(得分:1)

执行array.map { |x| x.reverse! }时,它更改了数组值。

array = ["batman","boobytrap"]

puts array.map { |x| x.reverse! }
=> namtab
=> partyboob

array
=> ["namtab", "partyboob"]

如果对同一个数组执行第二次操作,它将产生与问题中所述相同的结果。但是,它不会更改原始数组的值。

array = ["batman","boobytrap"]

puts array.map { |x| x.reverse! }
=> namtab
=> partyboob

array
=> ["namtab", "partyboob"]

puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap

array
=> ["namtab", "partyboob"]

要更改原始数组的值,请在第二次操作中使用map!

array = ["batman","boobytrap"]

puts array.map { |x| x.reverse! }
=> namtab
=> partyboob

array
=> ["namtab", "partyboob"]

puts array.map! { |x| x.reverse }
=> batman
=> boobytrap

array
=> ["batman", "boobytrap"]

答案 4 :(得分:0)

您的方法存在一些问题。

第一个问题是您没有正确隔离测试用例:在第一个测试用例中,您可以反转数组中的字符串。在您的第二个测试用例中,您再次将它们反转 。如果你反转两次会怎么样?那是对的:没有!所以,你认为它不起作用的原因实际上正是 工作的事实!如果没有工作(即没有反转字符串),那么它会打印以前反转的字符串,你会认为它 工作。< / p>

所以,第1课:始终隔离您的测试用例!

问题#2是第二段代码没有做你(可能)认为它做的事情。 x是一个局部变量(因为它以小写字母开头)。局部变量是它们定义的范围的本地变量,在本例中是块。因此,x仅存在于块内。你分配给它,但在分配之后你永远不会对它做任何事情。因此,作业实际上是无关紧要的。

相关的是块的返回值。现在,在Ruby中,赋值会计算分配的值,因此考虑到x的赋值是多余的,我们可以摆脱它,而你的代码实际上与此完全等价:

array.map { |x| x.reverse }

你的第三个问题是,第一件作品也没有做你(可能)认为它做的事情。 Array#map会返回 new 数组并保持原始数组不变,但String#reverse! 会突变字符串!换句话说:它的主要操作模式是副作用。除了副作用之外,会返回反向字符串,这是让你困惑的另一件事。它也可以返回nil来表示它执行副作用,在这种情况下,您将看到以下内容:

array = %w[batman boobytrap]

array.map(&:reverse!)
#=> [nil, nil]

array
#=> ['namtab', 'partyboob']

如您所见,如果 String#reverse! 返回nil,您会看到以下内容:

  • array.map返回 new 数组,其元素是块的返回值,仅为nil
  • array现在仍然包含与以前相同的String个对象,但它们已经发生变异

现在,由于String#reverse!实际上确实返回了反向String,您实际观察到的是:

array = %w[batman boobytrap]

array.map(&:reverse!)
#=> ['namtab', 'partyboob']

array
#=> ['namtab', 'partyboob']

array.map(&:reverse)
#=> ['batman', 'boobytrap']

array
#=> ['namtab', 'partyboob']

这让我第二课:副作用和共享的可变状态是邪恶的!

你应该尽量避免副作用和共享可变状态(理想情况下,一般是可变状态,但不同部分之间共享的可变状态尤其是邪恶)。

他们为什么邪恶?好吧,看看他们在这个极其简单的小例子中有多困惑?你能想象在一个更大,更大的应用程序中发生同样的事情吗?

副作用的问题在于它们发生在旁边&#34;。它们不是参数,它们不是返回值。您无法将它们打印出来,检查它们,将它们存储在变量中,在单元测试中对它们进行断言,等等。

共享可变状态的问题(突变只是副作用的一种形式,顺便说一句)是能够实现远距离的幽灵行为&#34;:你在一个地方有一个数据您的代码,但这些数据与代码的不同位置共享。现在,如果一个地方改变了数据,另一个地方似乎神奇地将他们的数据改变了。在这里的示例中,共享状态是数组中的字符串,并在一行代码中对它们进行变换,这使得它们在另一行代码中也会发生变化。