在Ruby中有“优势继续”的优雅方式吗?像这样:
method1(a, b)
.and_then(method2)
.and_then(method3)
.fail { |x| 'something is wrong'}
答案 0 :(得分:5)
我认为没有办法完全按照您展示的方式进行操作,但您可以使用begin
和rescue
块。
所以代码看起来像这样:
begin
method1(a,b).method2.method3
rescue
p "something is wrong"
end
在这三种方法中的任何一种方法中,只需调用
即可引发某种异常raise "Something is Wrong"
这将停止执行并运行救援块。如果要从发生raise
调用的执行上下文中获取某种数据,则需要实现自己的错误类型或使用现有类型。如果您希望这样做,救援声明需要更改如下
rescue => e
或者如果您有类型
rescue ArgumentError => e
这有点复杂,所以可以找到好的文章here
这仅适用于您实际调用的方法raise
例外情况。
答案 1 :(得分:4)
使用tap
,您可以“点击”方法链,以便对链中的中间结果执行操作。在tap
区块内,您可以放置条件。
method1(a, b).tap{|o| o.method2 if cond1}.tap{|o|o.method3 if cond2}
答案 2 :(得分:1)
如果您愿意保持开放的心态(想想方法返回值是成功还是失败),您可以这样做
https://github.com/pzol/deterministic
def works(ctx)
Success(1)
end
def breaks(ctx)
Failure(2)
end
def never_executed(ctx)
Success(99)
end
Success(0) >> method(:works) >> method(:breaks) >> method(:never_executed) # Failure(2)
甚至几乎可以使用问题中的确切伪语法!
require 'deterministic'
method1 = proc { |a, b| Deterministic::Result::Success(a * b) }
method2 = proc { Deterministic::Result::Success(2) }
method3 = proc { Deterministic::Result::Failure(3) } # error here, or whatever
error_handler = proc { |x| puts 'something is wrong'; Deterministic::Result::Success('recovered') }
res = method1.call(5, 6).
and_then(method2).
and_then(method3).
or_else(error_handler)
res # => Success("recovered")
# >> something is wrong
答案 3 :(得分:0)
通过"失败",我假设您的意思是其中一个链式方法返回一个对象(例如nil
),其方法在链中不包含以下方法。例如:
def confirm_is_cat(s)
(s=='cat') ? 'cat' : nil
end
然后:
confirm_is_cat('cat').upcase
#=> "CAT"
confirm_is_cat('dog').upcase
# NoMethodError: undefined method `upcase' for nil:NilClass
我们希望在第一种情况下返回"CAT"
,但要避免在第二种情况下引发异常。
一种方法是捕获异常,这已在其他答案中提及。有时可见的另一种方法是使用Enumerable#reduce(又名inject
)。我不能用这种方法提供一般的解决方案,但我会举一个例子,告诉你一般的想法。
假设我们向Fixnum
添加了四种方法。如果接收者是特定值(由方法名称指示),则每个方法返回nil
;否则它返回其接收器:
class Fixnum
def chk0; (self==0) ? nil : self; end
def chk1; (self==1) ? nil : self; end
def chk2; (self==2) ? nil : self; end
def chk3; (self==3) ? nil : self; end
end
我们希望从给定的整数n
开始并计算:
n.chk0.chk1.chk2.chk3
但如果chk0
,chk1
或chk2
返回nil
,请停止计算。我们可以这样做:
meths = [:chk0, :chk1, :chk2, :chk3]
def chain_em(meths,n)
meths.reduce(n) { |t,m| t && t.send(m) }
end
chain_em(meths,0)
#=> nil
chain_em(meths,1)
#=> nil
chain_em(meths,2)
#=> nil
chain_em(meths,3)
#=> nil
chain_em(meths,4)
#=> 4
现在假设我们还想确定哪个方法返回nil
,如果有的话(chk3
除外)。我们可以相应地修改方法:
def chain_em(meths,n)
last_meth = ''
meths.reduce(n) do |t,m|
if t.nil?
puts "#{last_meth} returned nil"
return nil
end
last_meth = t
t && t.send(m)
end
end
chain_em(meths,0)
#-> chk0 returned nil
#=> nil
chain_em(meths,1)
#-> chk1 returned nil
#=> nil
chain_em(meths,2)
#-> chk2 returned nil
#=> nil
chain_em(meths,3)
#=> nil
chain_em(meths,4)
#=> 4
答案 4 :(得分:0)
默认情况下,Ruby中没有这样的语法糖,但如果像这样的结构对你的项目有益,你可以将它添加到你自己的类中。在实施常规异常处理之前,花一些时间考虑它是否合适。
在下面的解决方案中,我向对象添加了一个标志,指示步骤是否失败,然后如果抛出异常则将其设置为true
。在方法链的末尾,failed?
方法检查它是否为true
,如果提供则生成block
。您可以根据您想要的任何条件设置标记。
请注意,Ruby中没有类范围的异常处理(并且可能有充分的理由),因此您需要为您认为相关的任何方法添加处理程序。
class Thing
def initialize(name)
@failed = false
@name = name
end
def change
begin
@name.capitalize!
rescue
@failed = true
end
self
end
def failed?
yield self if @failed && block_given?
@failed
end
end
thing = Thing.new(5)
# This will fail, because a Fixnum doesn't respond to capitalize!
thing.change.fail? { puts "Ooops." }
# Ooops.
您还可以将功能提取到module
中,并将其用作任何需要此行为的类的混合:
module Failable
@failed = false
def failed?
yield self if @failed && block_given?
@failed
end
def fail!
@failed = true
end
end
class Thing
include Failable
def initialize(name)
@name = name
end
def change
begin
@name.capitalize!
rescue
fail!
end
self
end
end