Proc 与 lambda

作者: 云莉6 | 来源:发表于2020-04-22 01:03 被阅读0次

    本文截取一段 ruby doc 官网 Proc 的内容并翻译,试图更好的理解和总结 Proc(non-lambda) 和 lambda 的区别和使用场景。

    Lambda and non-lambda semantics

    Procs are coming in two flavors: lambda and non-lambda (regular procs). Differences are:

    • In lambdas, return means exit from this lambda;

    • In regular procs, return means exit from embracing method (and will throw LocalJumpError if invoked outside the method);

    • In lambdas, arguments are treated in the same way as in methods: strict, with ArgumentError for mismatching argument number, and no additional argument processing;

    • Regular procs accept arguments more generously: missing arguments are filled with nil, single Array arguments are deconstructed if the proc has multiple arguments, and there is no error raised on extra arguments.

    p = proc {|x, y| "x=#{x}, y=#{y}" }
    p.call(1, 2)      #=> "x=1, y=2"
    p.call([1, 2])    #=> "x=1, y=2", array deconstructed
    p.call(1, 2, 8)   #=> "x=1, y=2", extra argument discarded
    p.call(1)         #=> "x=1, y=", nil substituted instead of error
    
    l = lambda {|x, y| "x=#{x}, y=#{y}" }
    l.call(1, 2)      #=> "x=1, y=2"
    l.call([1, 2])    # ArgumentError: wrong number of arguments (given 1, expected 2)
    l.call(1, 2, 8)   # ArgumentError: wrong number of arguments (given 3, expected 2)
    l.call(1)         # ArgumentError: wrong number of arguments (given 1, expected 2)
    
    def test_return
      -> { return 3 }.call      # just returns from lambda into method body
      proc { return 4 }.call    # returns from method
      return 5
    end
    
    test_return # => 4, return from proc
    

    直接翻译:

    Procs 有两种形式:lambdanon-lambda (regular procs)。它们的不同之处在于:

    • lambda 中,return 表示从该 lambda 退出;
    • 常规 proc 中,return 表示从该方法退出(如果在方法外部调用,将抛 LocalJumpError);
    • lambda 中,对参数的处理方式与方法相同:strict(严格),使用 ArgumentError 表示参数编号不匹配,并且不对附加参数进行处理;
    • 常规 proc 更慷慨地接受参数:缺少的参数填充为 nil,如果 proc 具有多个参数,则解构单个 Array 参数,并且在附加参数上不引发错误。

    个人更白话的理解:

    常规(非lambda)proc lambda
    return 退出整个 method return 仅从 lambda 内部退出
    慷慨地接受参数:缺少的参数填充为 nil,如果 proc 具有多个参数,则解构单个 Array 参数,并且在附加参数上不引发错误 strict(严格),使用 ArgumentError 表示参数编号不匹配,并且不对附加参数进行处理

    Lambdas are useful as self-sufficient functions, in particular useful as arguments to higher->order functions, behaving exactly like Ruby methods.

    直接翻译

    Lambda 作为自给自足的函数很有用,特别是作为高阶函数的参数时,与Ruby方法完全一样。ps: 这句话可以帮助牢记,lambda 的检查参数和 return lambda 自身函数的性质。

    Procs are useful for implementing iterators:

    def test
      [[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
                                #  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    end
    

    Inside map, the block of code is treated as a regular (non-lambda) proc, which means that the internal arrays will be deconstructed to pairs of arguments, and return will exit from the method test. That would not be possible with a stricter lambda.

    代码执行结果:

    2.4.4 :005 > def test
    2.4.4 :006?>     [[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
    2.4.4 :007?>                               #  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    2.4.4 :008?>   end
     => :test
    2.4.4 :009 > test
     => 5
    

    直接翻译

    Procs 在迭代中很有用,在 map 内部,代码块被视为常规(非lambda)proc,这意味着内部数组将被解构为成对的参数,而 return 将退出方法 test。如果使用更严格的 lambda,将不可能做到。

    个人白话理解

    Procs 这种常规(非lambda)proc 方式,在 map 这种迭代器中是很好的,上图看执行结果,可以实现在 map 内部直接 return 整个 test 方法。这比使用 lambda 更符合我们的代码意图。

    The only exception is dynamic method definition: even if defined by passing a non-lambda proc, methods still have normal semantics of argument checking.

    class C
     define_method(:e, &proc {})
    end
    
    C.new.e(1,2)       #=> ArgumentError
    C.new.method(:e).to_proc.lambda?   #=> true
    

    This exception ensures that methods never have unusual argument passing conventions, and makes it easy to have wrappers defining methods that behave as usual.

    class C
     def self.def2(name, &body)
       define_method(name, &body)
     end
    
    def2(:f) {}
    end
    C.new.f(1,2)       #=> ArgumentError
    

    The wrapper def2 receives body as a non-lambda proc, yet defines a method which has normal semantics.

    直接翻译

    唯一的例外是动态方法定义:即使通过传递 非lambda proc 进行定义,方法仍然具有参数检查的常规语义。此异常可确保方法永远不会具有异常的参数传递约定,并使包装器轻松定义照常运行的方法。包装器 def2 将主体作为 非lambda proc 接收,但定义了一种具有正常语义的方法。

    个人白话理解

    例外的是,即使通过非 lambda 的 proc 去动态定义 method,也会按照 lambda 的方式严格检查参数,这保证了动态定义方法的正常运行,不会出现和正常的 method 不同的行为。

    另外我也从 Rails 的 scope 为什么用 lambda?Proc 与 lambda 有什么不同之处? 这篇博文得到了些启发,我在记住这两种 Procs 不同点的时候,时常忘记到底是 常规 proc 检查参数还是 lambda,但如果能从「Rails 的 scope 为什么用 lambda?」这个问题来思考的话,就更加的能记忆清楚了。Railsscope 之所以用 lambda 就是因为 lambda 会严格检查参数可以避免 Proc 对于「缺少的参数填充为 nil」的行为导致 sql 查询有误。

    总结

    由上面分析可以看出,Proc(non-lambda)lambda 都有各自的用武之处,通过对代码的预期不同,来灵活使用从而达到准确实作功能的意图。从这点上来讲,深入了解两种 Procs 确实有助于我们写出更合适的代码并理解代码本身。

    相关文章

      网友评论

        本文标题:Proc 与 lambda

        本文链接:https://www.haomeiwen.com/subject/rdimihtx.html