Ruby 无副作用的散列操作方法
Ruby method operating on hash without side effects
我想创建一个将新元素添加到散列的函数,如下所示:
numbers_hash = {"one": "uno", "two": "dos", "three": "tres", }
def add_new_value(numbers)
numbers["four"] = "cuatro"
end
add_new_value(numbers_hash)
我读到不变性很重要,有副作用的方法不是一个好主意。明明这个方法是在修改原来的input,我应该怎么处理?
我不一定同意你应该始终避免变异的论点。特别是在您的示例的上下文中,突变似乎是该方法存在的唯一目的。因此它不是 side-影响 IMO。
当一个方法在做一些不相关的事情时更改输入参数时,我会称之为不需要的副作用,并且方法名称也不会明显改变输入参数。
您可能更喜欢return一个新的散列并保持旧的散列不变:
numbers_hash_1 = {"one": "uno", "two": "dos", "three": "tres", }
def add_new_value(numbers)
numbers.merge(four: "cuatro")
end
numbers_hash_2 = add_new_value(numbers_hash_1)
#=> {:one=>"uno", :two=>"dos", :three=>"tres", :four=>"cuatro"}
numbers_hash_1
#=> {:one=>"uno", :two=>"dos", :three=>"tres"}
引自Hash#merge
的文档:
merge(*other_hashes)
→ new_hash
Returns the new Hash
formed by merging each of other_hashes
into a copy of self
.
Ruby 是一种具有某些功能模式的 OOP 语言
Ruby 是一种面向对象的语言。副作用在 OO 中很重要。当您在对象上调用方法并且该方法修改对象时,这是一个副作用,这很好:
a = [1, 2, 3]
a.delete_at(1) # side effect in delete_at
# a is now [1, 3]
Ruby 还允许函数式样式,其中数据转换时没有副作用。您可能已经看到或使用过 map-reduce 模式:
a = ["1", "2", "3"]
a.map(&:to_i).reduce(&:+) # => 6
# a is unchanged
命令查询分离
可能让您感到困惑的是 Bertrand Meyers Command Query Separation Rule 发明的规则。这个规则说一个方法必须或者
- 有副作用,但没有return值,或者
- 没有副作用,但是return有点
但不是两者。请注意,虽然它被称为规则,但在 Ruby 中我会将其视为强有力的指导方针。有时违反此规则会产生更好的代码,但根据我的经验,大多数时候都可以遵守此规则。
我们必须澄清 Ruby 中“具有 return 值”的含义,因为每个 Ruby 方法都有一个 return 值——值它执行的最后一条语句(如果为空则为 nil)。我们的意思是该方法有一个 intentional return 值,它是该方法契约的一部分,调用者可以使用。
下面是一个具有副作用和 return 值的方法的示例,违反了此规则:
# Open the valve if possible. Returns whether or not the valve is open.
def open_valve
@valve_open = true if @power_available
@valve_open
end
以及如何将其分为两种方法以遵守此规则:
attr_reader :valve_open
def open_valve
@valve_open = true if @power_available
end
如果您选择遵守此规则,您可能会发现用动词短语命名副作用方法以及用名词短语命名 returning-something 方法很有用。这使得您从一开始就很清楚您正在处理哪种方法,并使命名方法更容易。
什么是副作用?
副作用是改变对象或外部实体(如文件)的状态。这种改变其对象状态的方法有一个副作用:
def register_error
@error_count += 1
end
这种改变参数状态的方法有副作用:
def delete_ones(ary)
ary.delete(1)
end
这种写入文件的方法有一个副作用:
def log(line)
File.open(log_path, "a") { |f| f.puts(line) }
end
我想创建一个将新元素添加到散列的函数,如下所示:
numbers_hash = {"one": "uno", "two": "dos", "three": "tres", }
def add_new_value(numbers)
numbers["four"] = "cuatro"
end
add_new_value(numbers_hash)
我读到不变性很重要,有副作用的方法不是一个好主意。明明这个方法是在修改原来的input,我应该怎么处理?
我不一定同意你应该始终避免变异的论点。特别是在您的示例的上下文中,突变似乎是该方法存在的唯一目的。因此它不是 side-影响 IMO。
当一个方法在做一些不相关的事情时更改输入参数时,我会称之为不需要的副作用,并且方法名称也不会明显改变输入参数。
您可能更喜欢return一个新的散列并保持旧的散列不变:
numbers_hash_1 = {"one": "uno", "two": "dos", "three": "tres", }
def add_new_value(numbers)
numbers.merge(four: "cuatro")
end
numbers_hash_2 = add_new_value(numbers_hash_1)
#=> {:one=>"uno", :two=>"dos", :three=>"tres", :four=>"cuatro"}
numbers_hash_1
#=> {:one=>"uno", :two=>"dos", :three=>"tres"}
引自Hash#merge
的文档:
merge(*other_hashes)
→new_hash
Returns the new
Hash
formed by merging each ofother_hashes
into a copy ofself
.
Ruby 是一种具有某些功能模式的 OOP 语言
Ruby 是一种面向对象的语言。副作用在 OO 中很重要。当您在对象上调用方法并且该方法修改对象时,这是一个副作用,这很好:
a = [1, 2, 3]
a.delete_at(1) # side effect in delete_at
# a is now [1, 3]
Ruby 还允许函数式样式,其中数据转换时没有副作用。您可能已经看到或使用过 map-reduce 模式:
a = ["1", "2", "3"]
a.map(&:to_i).reduce(&:+) # => 6
# a is unchanged
命令查询分离
可能让您感到困惑的是 Bertrand Meyers Command Query Separation Rule 发明的规则。这个规则说一个方法必须或者
- 有副作用,但没有return值,或者
- 没有副作用,但是return有点
但不是两者。请注意,虽然它被称为规则,但在 Ruby 中我会将其视为强有力的指导方针。有时违反此规则会产生更好的代码,但根据我的经验,大多数时候都可以遵守此规则。
我们必须澄清 Ruby 中“具有 return 值”的含义,因为每个 Ruby 方法都有一个 return 值——值它执行的最后一条语句(如果为空则为 nil)。我们的意思是该方法有一个 intentional return 值,它是该方法契约的一部分,调用者可以使用。
下面是一个具有副作用和 return 值的方法的示例,违反了此规则:
# Open the valve if possible. Returns whether or not the valve is open.
def open_valve
@valve_open = true if @power_available
@valve_open
end
以及如何将其分为两种方法以遵守此规则:
attr_reader :valve_open
def open_valve
@valve_open = true if @power_available
end
如果您选择遵守此规则,您可能会发现用动词短语命名副作用方法以及用名词短语命名 returning-something 方法很有用。这使得您从一开始就很清楚您正在处理哪种方法,并使命名方法更容易。
什么是副作用?
副作用是改变对象或外部实体(如文件)的状态。这种改变其对象状态的方法有一个副作用:
def register_error
@error_count += 1
end
这种改变参数状态的方法有副作用:
def delete_ones(ary)
ary.delete(1)
end
这种写入文件的方法有一个副作用:
def log(line)
File.open(log_path, "a") { |f| f.puts(line) }
end