为什么我不能在 Ruby 代码块中重新分配变量?
Why can't I reassign a variable in a Ruby code block?
为什么调用这两个 .map 方法不会产生相同的结果?第一个按预期工作,而第二个没有效果。
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap
问题是在您的第一张地图中,!已修改原始数组中的值,因此它现在包含反转的字符串。
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"]
所以第二次是如你所愿,但入口数据不是你想的那样。
如果您在不执行第一种情况的情况下独立尝试第二种情况,您会发现它按预期工作。
在 puts array.map { |x| x.reverse! }
之后打印 array
。您会看到 - 数组已更改。
阅读 documentation 以了解 reverse!
方法。
你必须改变你对这里变量的看法。该变量不是实际值,而只是对该值的引用。
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 val 的值不再在 70244773377800 中找到,而是在 70244761504360 中找到。尽管该数组仍然引用它的第一个值 70244773377800!
另一方面,您在 x 上的示例中使用的 #reverse!
方法调用会更改内存中 70244773377800 处找到的任何值,这就是它按预期工作的原因。
TLDR;
您的第一个示例更改内存中的值,而第二个示例将新的内存地址分配给局部变量。
当您执行 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"]
你的方法有几个问题。
第一个问题是您没有正确隔离测试用例:在您的第一个测试用例中,您反转了数组中的字符串。在您的第二个测试用例中,您再次 反转它们。如果你将某件事反转两次会怎样?没错:没有!因此,您 认为 它不起作用的原因实际上恰恰是它 起作用的事实!如果它 没有 工作(即不反转字符串),那么它会打印先前反转的字符串,你会认为它 确实 工作。
因此,第 1 课:始终隔离您的测试用例!
问题 #2 是第二段代码没有按照您(可能)认为的那样做。 x
是局部变量(因为它以小写字母开头)。局部变量在它们定义的范围内是局部的,在本例中是块。所以,x
只存在于块内。你分配给它,但在分配给它之后你永远不会对它做任何事情。所以,赋值实际上是无关紧要的。
与相关的是块的return值。现在,在 Ruby 中,赋值计算为被赋值的值,因此考虑到 x
的赋值是多余的,我们可以去掉它,而你的代码实际上完全等同于这个:
array.map { |x| x.reverse }
你的第三个问题是第一部分也没有按照你(可能)认为的那样去做。 Array#map
returns a new array and leaves the original array untouched, but String#reverse!
突变 字符串!换句话说:它的主要运作模式是副作用。除了副作用之外,它 还 return 是反转的字符串,这是另一件让您感到困惑的事情。它也可以 return nil
而不是表示它执行副作用,在这种情况下,您会看到以下内容:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> [nil, nil]
array
#=> ['namtab', 'partyboob']
如你所见,if String#reverse!
did return nil
,你会观察到以下内容:
array.map
returns 一个 new 数组,其元素是块的 return 值,也就是 nil
array
现在仍然包含与以前相同的 String
个对象,但它们已发生变异
现在,由于 String#reverse!
实际上 return 相反 String
你实际观察到的是:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> ['namtab', 'partyboob']
array
#=> ['namtab', 'partyboob']
array.map(&:reverse)
#=> ['batman', 'boobytrap']
array
#=> ['namtab', 'partyboob']
这让我想到第 2 课:副作用和共享可变状态是邪恶的!
你应该尽可能避免副作用和共享可变状态(理想情况下,一般来说是可变状态,但不同部分之间共享的可变状态尤其邪恶)。
为什么他们是邪恶的?好吧,看看他们在这个极其简单的小例子中有多少困惑?您能想象在一个大得多的应用程序中发生同样的事情吗?
副作用的问题在于它们 "happen on the side"。它们不是参数,它们不是 return 值。您不能将它们打印出来、检查它们、将它们存储在变量中、在单元测试中对它们进行断言等等。
共享可变状态的问题(顺便说一句,变异只是一种副作用)是启用"spooky action at a distance":你的代码的一个地方有一段数据,但是这个一段数据与代码的不同位置共享。现在,如果一个地方改变了数据,另一个地方似乎会神奇地让他们的数据在他们下面发生变化。在您此处的示例中,共享状态是数组中的字符串,并且在一行代码中对它们进行变异会使它们在另一行代码中也发生变化。
为什么调用这两个 .map 方法不会产生相同的结果?第一个按预期工作,而第二个没有效果。
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap
问题是在您的第一张地图中,!已修改原始数组中的值,因此它现在包含反转的字符串。
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"]
所以第二次是如你所愿,但入口数据不是你想的那样。 如果您在不执行第一种情况的情况下独立尝试第二种情况,您会发现它按预期工作。
在 puts array.map { |x| x.reverse! }
之后打印 array
。您会看到 - 数组已更改。
阅读 documentation 以了解 reverse!
方法。
你必须改变你对这里变量的看法。该变量不是实际值,而只是对该值的引用。
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 val 的值不再在 70244773377800 中找到,而是在 70244761504360 中找到。尽管该数组仍然引用它的第一个值 70244773377800!
另一方面,您在 x 上的示例中使用的 #reverse!
方法调用会更改内存中 70244773377800 处找到的任何值,这就是它按预期工作的原因。
TLDR; 您的第一个示例更改内存中的值,而第二个示例将新的内存地址分配给局部变量。
当您执行 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"]
你的方法有几个问题。
第一个问题是您没有正确隔离测试用例:在您的第一个测试用例中,您反转了数组中的字符串。在您的第二个测试用例中,您再次 反转它们。如果你将某件事反转两次会怎样?没错:没有!因此,您 认为 它不起作用的原因实际上恰恰是它 起作用的事实!如果它 没有 工作(即不反转字符串),那么它会打印先前反转的字符串,你会认为它 确实 工作。
因此,第 1 课:始终隔离您的测试用例!
问题 #2 是第二段代码没有按照您(可能)认为的那样做。 x
是局部变量(因为它以小写字母开头)。局部变量在它们定义的范围内是局部的,在本例中是块。所以,x
只存在于块内。你分配给它,但在分配给它之后你永远不会对它做任何事情。所以,赋值实际上是无关紧要的。
与相关的是块的return值。现在,在 Ruby 中,赋值计算为被赋值的值,因此考虑到 x
的赋值是多余的,我们可以去掉它,而你的代码实际上完全等同于这个:
array.map { |x| x.reverse }
你的第三个问题是第一部分也没有按照你(可能)认为的那样去做。 Array#map
returns a new array and leaves the original array untouched, but String#reverse!
突变 字符串!换句话说:它的主要运作模式是副作用。除了副作用之外,它 还 return 是反转的字符串,这是另一件让您感到困惑的事情。它也可以 return nil
而不是表示它执行副作用,在这种情况下,您会看到以下内容:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> [nil, nil]
array
#=> ['namtab', 'partyboob']
如你所见,if String#reverse!
did return nil
,你会观察到以下内容:
array.map
returns 一个 new 数组,其元素是块的 return 值,也就是nil
array
现在仍然包含与以前相同的String
个对象,但它们已发生变异
现在,由于 String#reverse!
实际上 return 相反 String
你实际观察到的是:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> ['namtab', 'partyboob']
array
#=> ['namtab', 'partyboob']
array.map(&:reverse)
#=> ['batman', 'boobytrap']
array
#=> ['namtab', 'partyboob']
这让我想到第 2 课:副作用和共享可变状态是邪恶的!
你应该尽可能避免副作用和共享可变状态(理想情况下,一般来说是可变状态,但不同部分之间共享的可变状态尤其邪恶)。
为什么他们是邪恶的?好吧,看看他们在这个极其简单的小例子中有多少困惑?您能想象在一个大得多的应用程序中发生同样的事情吗?
副作用的问题在于它们 "happen on the side"。它们不是参数,它们不是 return 值。您不能将它们打印出来、检查它们、将它们存储在变量中、在单元测试中对它们进行断言等等。
共享可变状态的问题(顺便说一句,变异只是一种副作用)是启用"spooky action at a distance":你的代码的一个地方有一段数据,但是这个一段数据与代码的不同位置共享。现在,如果一个地方改变了数据,另一个地方似乎会神奇地让他们的数据在他们下面发生变化。在您此处的示例中,共享状态是数组中的字符串,并且在一行代码中对它们进行变异会使它们在另一行代码中也发生变化。