【问题标题】:How to return true if one statement in a block returns true in Ruby?如果块中的一个语句在 Ruby 中返回 true,如何返回 true?
【发布时间】:2011-07-30 02:06:18
【问题描述】:

是否可以在 Ruby 中创建一个方法,该方法接受一个块并运行块中的每个语句,直到其中一个返回 false?

done = prepare_it do |x|
  method_1
  method_2
  method_3
end
puts "Something did not work" unless done

我希望函数 prepare_it 运行每个语句,但如果其中一个语句失败,函数就会退出。如果函数返回true,则表示所有步骤都成功。

这似乎是在调用异常,但我不确定如何触发和处理它。我希望我不必修改 method_x 函数以在失败时抛出异常,而是让 prepare_it 在 method_x 之一失败时抛出异常(返回 false)

编辑: 感谢您的建议,我想我需要为我的问题添加更多细节。这是我第一次尝试元编程和创建 DSL。 :)

我有一个类可以向路由器发送命令。有许多步骤总是需要按顺序执行(连接、登录、通过等)。我认为如果用户愿意,让他们能够更改命令的顺序会很好。例如,有些路由器不询问用户,直接进入密码提示。其他人则完全跳过登录。在某些情况下,您需要提升权限或设置终端选项。

但是,如果任何命令失败,应该有一种机制使整个块失败。因此,如果由于某种原因密码失败(该方法返回 false/nil),则继续执行其余命令是没有意义的。 我应该标记它失败了。

&& 方法有效,但我认为它不是一个很好的界面。

也许我应该强迫用户给我更小的块,一次一个,然后将它们放入堆栈中,然后运行命令一个接一个地产生,而不是获得一个大块?

my_router.setup do {send_pass}
my_router.setup do {set_terminal}
my_router.setup do {enable_mode}
my_router.run_setup

如果可以的话,我觉得会超级干净

my_router.setup do |cmd|
  cmd.send_pass
  cmd.set_terminal
end
puts "Done" if my_router.ready?

所以欢迎在幕后发生的任何魔术! :)

【问题讨论】:

    标签: ruby


    【解决方案1】:

    解决方案

    done = prepare_it do |x|
      method_1 && method_2 && method_3
    end
    puts "Something did not work" unless done
    

    说明

    && 运算符 “短路” 因此,如果 method_1 返回 false,则不会调用 2 和 3,而 done 将是 false

    示例

    http://gist.github.com/1115117

    【讨论】:

    • 短路真的会像这样工作吗?这不是也需要method_1、method_2和method_3返回一个布尔值吗?我的 Ruby 非常生锈,但这对我来说似乎很可疑。
    • 它确实有效,这是要点,您可以自己查看gist.github.com/1115117
    • 如果其中一种方法返回 ruby​​ 认为的 false,就会发生短路,这是(惊喜!)falsenil。每隔一个返回值将评估为 true 导致调用继续
    • 无论method_3 的值是多少,返回值要么为假,要么method_1 和method_2 不为nil 或假。由于除了 nil 或 false 之外的任何值都会导致 if 块被执行,这非常令人满意。
    • 没错,将它们短路就可以了。有什么方法可以将块转换为 && 语句?或者我应该让用户写 done=my_router.send_pass && my_router.set_term && my_router.set_width
    【解决方案2】:

    没有一个好的方法可以破解这样的工作。我会执行以下操作之一:

    done = prepare_it do |x|
      method_1 && method_2 && method_3
    end
    

    现在,在某些特定情况下,您可以使用魔术来完成这项工作。例如,如果应该在块参数x 上调用方法:您可以执行以下操作:

    class SuccessProxy
      def initialize(obj)
        @object = obj
      end
    
      def method_missing(meth, *args, &block)
        @object.send(meth, *args, &block) || raise SuccessProxy::Exception
      end
    
      class Exception < ::Exception
      end
    end
    
    def prepare_it
      # however you usually generate x
      yield SuccessProxy.new(x)
    rescue SuccessProxy::Exception
      false
    end
    
    prepare_it do |x|
      x.method_1
      x.method_2
      x.method_3
    end
    

    或者,如果要像method_1 那样在上下文中的默认对象上调用方法,则可以使用instance_eval 而不是yield 将代理置于范围内。

    最后,如果你真的想要花哨,你实际上可以使用 Ruby 解析器来解析单独的语句。

    但老实说,我不相信您真的想将元编程作为一种工具。也许如果您提供更详细的信息,我会对最佳解决方案更有帮助,但它可能类似于上面的第一个代码示例。

    【讨论】:

    • 我不确定我是否完全了解这里发生的事情。似乎只有在缺少方法时才会起作用,而不是在方法返回 false 时才起作用。
    • SuccessProxy 对象没有定义任何方法(好吧,除了 Object 的默认值,如果需要,您可以取消定义这些方法)。它将所有方法调用发送到实际对象,如果它们的返回值评估为 false(从技术上讲,是 falsenil),则会引发异常。
    【解决方案3】:

    自然紧凑将是你的首要任务,所以我相信这是最好的答案:

    done = (1..3).all? { |n| send "method_#{n}" }
    puts "Something did not work" unless done
    

    【讨论】:

    • 谢谢,但是 method_1,2,3 只是方法真实名称的占位符:)
    • 正确性、完整性、紧凑性。标题是“一个声明”,意思是any?,但从那时起进行了很多修改。
    【解决方案4】:

    如果你使用异常,它就不那么漂亮了,但你不必依赖返回值:

    done = prepare_it do |x|
      begin
        method_1
        method_2
        method_3
        true
      rescue
        false
      end
    end
    puts "Something did not work" unless done
    
    def method_1
      # No issues, doesn't matter what we return
      return true
    end
    
    def method_2
      # Uh-oh, problem
      raise
    end
    

    【讨论】:

    • 纯粹的邪恶。例外是为特殊情况保留的。
    【解决方案5】:

    起初我想,“如果这是 Lisp 会不会很好......”但这让我意识到了确切的问题。做你想做的事(在一个块中执行每个步骤,直到一个为假)会很好,但需要访问 AST。

    你的想法

    my_router.setup do {send_pass}
    my_router.setup do {set_terminal}
    my_router.setup do {enable_mode}
    my_router.run_setup
    

    正在尝试完全按照您在 Lisp 中所做的工作——构建一个列表并将该列表交给其他东西来执行每件事,直到您得到一个错误的返回值。

    如果您只是在没有任何参数的情况下调用类中的方法,那么作为一种解决方法来定义带符号的东西怎么样?:

    class Router
      def run_setup *cmds
        # needs error handling
        while c = cmds.shift
          return false unless send(c)
        end
      end
    end
    
    my_router.run_setup :send_pass, :set_terminal, :enable_mode
    

    【讨论】:

    猜你喜欢
    • 2019-08-16
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 2016-02-12
    • 2012-09-08
    • 2020-01-20
    • 2016-01-04
    • 1970-01-01
    相关资源
    最近更新 更多