Ruby & RailsRubyRuby on Rails

Tap, Inject and Each_with_object

2017-08-27  本文已影响43人  老码农不上班

Ruby 中 Tap, Inject 和 Each_with_object

中文翻译说明:

  1. 分析 inject 和 Each_with_object 时用到“叠加器”这个词,其代表调用这两个方时,块中的 memo 和 memo_obj 参数
  2. 类枚举类型指的是 Hash, Array 等能够被遍历的数据类型

在工作中使用 Ruby 作为主编程语言两年有余,从刚开始的惊讶到喜欢其优雅,这篇文章总结三个有助于编写可读性强且简洁的代码的方法,重构代码时会经常用到。

Object#tap

tap 方法把当前的对象传给块(block), 并且返回当前调用对象。返回值为当前调用对象的模式能够继续调用对象的公有方法,以此形成了一种链式调用的效果。

查看源码,其实现如下:

class Object
  def tap
    yield self
    self
  end
end

在来看看一个实际的例子,假设在 Rails controller 中我们需要对用户传递的参数处理,如果没有使用 tap ,我们可能会写出这样的代码

def update_params(params)
  params[:foo] = "bar"
  params
end

对一个参数值处理了之后,改方法返回参数哈希。但是现在我们知道 tap 方法会返回对象,所以改造之后的方法如下:

def update_params(params)
  params.tap {|p| p[:foo] = "bar"}
end

改造之后的代码是不是很简洁优雅?不言而明~
另外再来看看充分显示链式调用的例子:

User
  .active                      .tap { |users| puts "Users so far: #{users.size}" }
  .non_admin                   .tap { |users| puts "Users so far: #{users.size}" }
  .at_least_years_old(25)      .tap { |users| puts "Users so far: #{users.size}" }
  .residing_in('USA')

Enumerable#each_with_object #inject methods

枚举类型中这两个方法用的好能极大地优化代码,但同时因为忽略两者的差异写出有 bug 的代码。

inject

通过二元运算对枚举类型(Array, Hash ...)的所有元素操作,可通过块(block),或传符号(symbol) 的参数调用。
【这里的符号可以是方法名或者操作符,例如 :method_name, :+ 但请注意,:operator 的方式使用比较多】

每遍历一个元素,就会把块的返回值赋值给 memo,修改了叠加器的值,改方法最后的返回值为最后一次遍历块的返回值,如果想返回叠加器的值,应当在块中返回叠加器(下文有例子讨论)

inject(initial, sym) → obj
inject(sym) → obj
inject(initial) { |memo, obj| block } → obj
inject { |memo, obj| block } → obj

有两种使用方式:传递符号参数调用(symbol)、块调用(block)。

inject 的返回值是最后遍历枚举类型之后,块返回的值。通常情况下,我们期望最后得到叠加器(在例子中体现在 sum 和 memo),虽然把每次遍历计算出来的叠加器的值放在块的最后也可以做到(在举例时我们在块中返回了 sum 和 memo.merge!(user.name => user.phone))显然这不符合 Ruby 程序的优雅。有没有一种方法直接遍历枚举类型结束后返回叠加器的值而不是块的值呢?
Enumerable#each_with_object 能实现。

each_with_object

each_with_object(obj) { |(*args), memo_obj| ... } → obj
each_with_object(obj) → an_enumerator

each_with_object 的返回值是 obj 原始对象,所以不需像 inject 一样在块中返回 迭代器的值。

users.each_with_object({}) do |user, memo| # note object and memo reversed from #inject
    memo[user.name] = user.email
end

参考阅读

上一篇下一篇

猜你喜欢

热点阅读