如果值不是nil,有条件地在Ruby中执行块? (又名Smalltalk' ifNotNilDo :)

时间:2014-10-26 20:13:55

标签: ruby design-patterns block smalltalk

在Smalltalk中有方法ifNotNilDo: 它的使用方式如下:

database getUser ifNotNilDo: [:user | Mail sendTo: user ]

在不执行块nil的对象上,将对象本身作为参数传递。类UndefinedObject(Smalltalk相当于Ruby的NilClass)中的实现根本不起作用。这样,如果让用户产生nil对象,则不会发生任何事情。

我不知道Ruby的类似内容,所以我推出了自己的解决方案。 它是这样的:

class Object
  def not_nil
    yield(self)
  end
end

class NilClass
  def not_nil
    # do nothing
  end
end

可以像这样使用:

users = {:peter => "peter@peter.com", :roger => "roger@roger.com" }
users[:roger].not_nil {|u| Mail.send(u) }

这使我们无法访问哈希两次

Mail.send(users[:roger]) if users[:roger]

...或使用临时变量:

u = users[:roger]
Mail.send(u) if u

更新

人们开始建议基于哈希操作的解决方案,并且还要两次访问哈希。我的问题不是直接与哈希相关。

想象一下,第一个操作不是哈希访问,而且也很昂贵。像:

RemoteUserRepo.find_user(:roger).not_nil {|u| Mail.send(u) }

(结束更新)

我的问题是:

  • 重新发明这个成语我错了吗?
  • Ruby中是否支持这样的(或更好的)?
  • 还是有另一种更短的更优雅的方式吗?

6 个答案:

答案 0 :(得分:11)

在Ruby 2.3.0+中,您可以将安全导航操作符(&.)与Object#tap结合使用:

users[:roger]&.tap { |u| Mail.send(u) }

答案 1 :(得分:4)

您可以使用tap来避免两次哈希访问:

users[:roger].tap { |u| Mail.send(u) if u }

我可能会使用这样的东西:

[ users[:roger] ].reject(&:nil?).each { |u| Mail.send u }

答案 2 :(得分:4)

在ActiveSupport中,有try方法。 https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb

data = { a: [1,2,3] }
data[:a].try(:first)
#=> 1
data[:b].try(:first)
#=> nil
data[:b].first
#=> Exception

在幕后,它是靠近你的解决方案实施的。对于任何对象,只有零,它将“发送消息”(就Smalltalk而言)具有属性。

# object.rb
def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    public_send(*a, &b) if respond_to?(a.first)
  end
end

# nilclass
def try(*args)
  nil
end

关于您的问题

重新发明这个成语我错了吗?

Rails的家伙做了类似的事情

Ruby中是否支持这样(或更好)的东西?

不,Ruby不支持它开箱即用

还是有其他更短更优雅的方法吗?

在我看来它有一个问题:程序员应该控制数据。人们应该知道他拥有什么样的数据并处理每种类型和每种情况,或者引发错误。在您的情况下,它适用于所有数据类型,但NilClass。什么可能导致很难调试的错误。

我更喜欢使用老式的

Mail.send(users[:roger]) if users[:roger]
# or
users[:roger] && Mail.send(users[:roger])
# or use caching if needed

答案 3 :(得分:2)

在像Ruby这样的函数式语言中,有一个惯用的解决方案,它利用了赋值语句返回可以测试的值的事实:

unless (u = users[:roger]).nil?
  Mail.send(u)
end

因此,您可以根据需要避免额外的哈希查找。 (然而,一些功能纯粹主义者不赞成这种事情,因为它测试副作用声明的返回值。)

答案 4 :(得分:2)

不,重新发明这个成语你没有错。您可能仍然可以更好地为其命名,可能是if_not_nil

是的,有一种核心的Ruby方法可以做到这一点,虽然它并没有完全渗透优雅:

[RemoteUserRepo.find_user(:roger)].compact.each {|u| Mail.send(u)}

回想一下compact返回一个删除了nil元素的数组副本。

答案 5 :(得分:1)

users.delete_if { |_, email| email.nil? }.each { |_, email| Mail.send email }

users.values.compact.each { |email| Mail.send email }