添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Parameters : Represents the expected values to be passed to a method when messaged as defined when the method is implemented. Example: def demo one, two: nil .

Arguments : Represents the actual values passed to the method when messaged. Example: demo 1, two: 2 .

With the above in mind, this article will dig deeper into how parameters and arguments work in Ruby since there are subtleties that might not be obvious unless you’re doing more complex logic and/or metaprogramming.

Table of Contents
  • Parameters
  • Basic
  • Forwards
  • Splats
  • Anonymous
  • Named
  • Empty
  • No Keywords
  • Keyword Fallbacks
  • Keyword Overrides
  • Parenthesis
  • Primitives
  • Procs and Lambdas
  • Arguments
  • Basic
  • Splats
  • Super
  • Message Delegation
  • Argument Forwarding
  • Splats
  • Destructuring
  • Marameters
  • There is important terminology to be aware of when defining method parameters and later passing arguments to the same method. I’ve split the terminology into several sections starting at a high level and working further down into specifics.

    Basic

    Basic parameters are those which you are most likely to be aware of and use on a daily basis. Consider the following code where all possible parameters are used:

    def demo(one, two = :b, *three, four:, five: :e, **six, &seven) = super

    The above leverages the basic parameters you can define for a method in order of precedence . If we inspect the method’s parameters and then use Amazing Print to render the response, things become more clear since we get an array of tuples (i.e. kind and name ):

    ap method(:demo).parameters
    #   [
    #     :req,
    #     :one
    #   ],
    #   [
    #     :opt,
    #     :two
    #   ],
    #   [
    #     :rest,
    #     :three
    #   ],
    #   [
    #     :keyreq,
    #     :four
    #   ],
    #   [
    #     :key,
    #     :five
    #   ],
    #   [
    #     :keyrest,
    #     :six
    #   ],
    #   [
    #     :block,
    #     :seven
    #   ]
    

    The kinds of parameters allowed can be split into three categories:

    opt: An optional parameter that always has a default value and should only be defined after a required positional parameter.

    rest: Optional parameters — also known as single splats (*) — which can be any number of parameters after your optional positionals. ⚠️ Heavy use of this parameter is considered a code smell because, when unchecked, can get quite large so use with care or avoid altogether.

    key: An optional parameter that always has a default value and should only be defined after a required keyword parameter.

    keyrest: Optional parameters — also known as double splats (**) — which can be any number of parameters that come after your optional keywords. ⚠️ Heavy use of this parameter is considered a code smell because, when unchecked, can get quite large so use with care or avoid altogether.

    Block: You can only define a single block parameter and must be in the last position. In our case, the block parameter is explicitly named seven even though most people tend to use block as the parameter name. A deeper dive into blocks is out of scope for this article but you can learn more about them in my Ruby Function Composition article. In the meantime, here’s a quick explanation:

    Implicit: All methods can implicitly accept a block. Example: demo { "Example." }. Implicit blocks make for an elegant design when coupled with block_given? in order to yield to the block passed to your method.

    Explicit: If you want to explicitly reference a block, then you must name it. Example: def demo(&block). Explicit blocks are great when you need to use the block directly without the need to check if a block has been implicitly defined.

    Anonymous: Anonymous blocks were first introduced in Ruby 3.1.0 and are great for forwarding to another method without needing to name them first. Example: def demo(&).

    While the above is a simple example that only uses a leading required positional parameter, you can definitely lead with both required and optional positional arguments as well. Keywords and blocks are not allowed, though, and will cause a syntax error.

    The kinds and names of these parameters are important because they allow you to pattern match and dynamically distinguish between a method using forwarding, anonymous splats/blocks, or named parameters.

    Splats

    There are three kinds of parameter splats: forwards, anonymous, or named. You’ve already seen how forwarding works and to illustrate further, the following table provides a visual breakdown of the different kinds of splats in Ruby 3.2.0 and higher:

    Prior to the release of Ruby 3.2.0, forwarding of bare splats wasn’t supported which meant a method signature of def demo(*, **) would result in the following parameters: [[:rest], [:keyrest]]. With Ruby 3.2.0 and higher, using forwarded or anonymous parameters always results in identical parameters as emphasized above: [[:rest, :*], [:keyrest, :**]].

    We’ll dive into the details of each kind of parameter next.

    Anonymous

    Anonymous splats — also known as bare splats — are denoted by use of a single splat (*), double splat (**), and/or the bare block (&). The single splat is an Array while the double splat is a Hash. Here’s an example:

    def demo(*, **, &) = super
    ap method(:demo).parameters
    #   [
    #     :rest,
    #     :*
    #   ],
    #   [
    #     :keyrest,
    #     :**
    #   ],
    #   [
    #     :block,
    #     :&
    #   ]
    

    Prior to the release of Ruby 3.2.0, bare splats never had a name so you’d only get information about the kind of splat which meant you couldn’t do much else with them. With Ruby 3.2.0 and higher, bare splats can be forwarded in addition to other benefits:

    You can use super — as shown in the example above — to pass the splatted arguments upwards for further processing.

    You can forward the single, double, and/or block splat to another method. Example: def demo(*, **, &) = my_method(*, **, &).

    You can gobble positional and/or keyword arguments into a black hole so-to-speak so they are never used. This can be handy when coupled with the Null Object Pattern where you ignore the incoming splatted arguments entirely.

    def demo(*, **, &) = puts "Positionals: #{[*]}, Keywords: #{{**}}, Block: #{proc(&)}."
    demo(1, 2, c: 3, d: 4) { "example" }
    # Positionals: [1, 2], Keywords: {:c=>3, :d=>4}, Block: #<Proc:0x000000012c293128 (irb):5>.

    With the positional and keyword arguments, you get immediate inspection. With the block, you get partial inspection because you can see if you are dealing with a proc or a lambda. Alternatively, you could call the block for deeper inspection:

    def demo(*, **, &) = puts "Positionals: #{[*]}, Keywords: #{{**}}, Block: #{proc(&).call}."
    demo(1, 2, c: 3, d: 4) { "example" }
    # Positionals: [1, 2], Keywords: {:c=>3, :d=>4}, Block: example.

    As you can see, we get kind and name information which means we can make use of them within the method like any other argument.

    The difference between forwards, anonymous and named splats is important to know when inspecting a method’s behavior or dynamically building a list of arguments for messaging.

    Empty

    Single and double splats can be implicitly coerced into an Array and Hash, respectively, when given no arguments. Example:

    def demo(*positionals, **keywords) = puts "Positionals: #{positionals}, Keywords: #{keywords}"
    demo # Positionals: [], Keywords: {}

    Behavior is different when passing nil, though. Example:

    demo *nil   # Positionals: [], Keywords: {}
    demo **nil  # no implicit conversion of nil into Hash (TypeError)

    At the moment — and unlike an Array — **nil can’t be implicitly cast into a Hash and will raise a TypeError. Starting in Ruby 3.4.0, this is fixed so you can pass *nil and **nil as arguments and, implicitly, get an empty array and hash.

    You will get a RuboCop Style/OptionalArguments error if attempting to use this parameter.

    You can’t use a single splat positional argument after a post parameter is defined. Although, you can use required, optional, and double splat keyword parameters as well as a block.

    Performance-wise, Ruby can’t optimize post parameters like it can for required and optional parameters.

    def demo(positional_name = nil, name: positional_name) = puts "Name: #{name}."
    demo "Test"        # "Name: Test"
    demo name: "Test"  # "Name: Test"

    Notice you can pass in the "Test" value either as a positional argument or keyword argument and get the same result. In the case of using a positional argument, the value will become the default value for the keyword parameter. This can be handy when wanting to use a positional parameter as a fallback for your keyword parameter or wanting to refactor using keyword parameters instead of positional parameters. While this is worth knowing, there are several caveats to watch out for since this can lead to problematic code:

    When passing in both a positional and keyword argument, the keyword argument will take precedence due to being evaluated last. For example, using demo "One", name: "Two" will print the value of "Two" instead of "One".

    When wanting to convert multiple positional parameters into keyword parameters, you can end up with a very long method signature that will quickly violate the rule of three for maximum number of parameters used.

    You should add a deprecation warning when the positional parameter is set to encourage people to update their code to using the keyword arguments instead. This will allow people to upgrade while also setting a date in which you delete positional parameter support to keep code complexity at a minimum.

    def demo begin: 1, end: 10
      puts "Begin: #{binding.local_variable_get(:begin)}, End: #{binding.local_variable_get(:end)}."
    demo  # "Begin: 1, End: 10."

    You can clean this up further by using the Refinements gem:

    require "refinements/binding"
    using Refinements::Binding
    def demo(begin: 1, end: 10) = puts "Begin: #{binding[:begin]}, End: #{binding[:end]}."
    # "Begin: 1, End: 10."

    If refining isn’t your cup of team, you could use hash pinning:

    def demo begin: 1, end: 10
      attributes = {begin:, end:}
      puts "Begin: #{attributes[:begin]}, End: #{attributes[:end]}."
    demo  # "Begin: 1, End: 10."

    While hash pinning might be slightly awkward, there can be a performance boost as shown in these benchmarks.

    While the above provides a working solution, there are important caveats to be aware of:

    This solution is only possible when using keyword parameters and does not work for positional parameters.

    There is precedence for this use case but even the core language is not the best teacher so tread with caution.

    Applying this solution to your own code should be done with caution because heavy use of binding can get unwieldy and cumbersome quickly. Granted, this can be useful in Domain Specific Languages (DSLs) or adding a touch of syntactic sugar to your method signatures in rare situations but you’d only want to reach for this when all other options have been exhausted.

    This usage is more an antipattern because the required positional parameter (array) is subtle and will cause people to have a hard time reading and understanding the code. At the same time, this does give you a way to define the shape of the required positional parameter. Other complications, such as shared assignment, is another concern. Take the following:

    def demo_a(first, second = first) = [first, second]
    def demo_b((first, second), third = first) = [first, second, third]
    # Default assignment works as expected.
    demo_a 1       # [1, 1]
    # Default assignment fails due to a bug with parameter destructuring.
    demo_b [1, 2]  # [1, 2, nil]

    In general — and due to these complications — use of parenthesis is not a recommended practice.

    Primitives

    Ruby primitives — such as String, Integer, Struct, and so forth — are written in C, not Ruby. Sadly, this means asking for a primitive’s parameters will give you false information. A good example is using an inline Struct:

    ap Struct.new(:one, :two).method(:new).parameters
    #   [
    #     :rest
    #   ]
    

    If you use Structs a lot — or any primitive written in C — you know this information is incorrect because a struct can accept both positional and keyword arguments. The problem is the argument conversion is done in C, not Ruby. This is another aspect of Ruby where asking for a method’s parameters won’t help you but is important to be aware of. There is a long standing Ruby Issue, if fixed, would solve this problem and lead to more informative parameter information.

    One trick to solving this situation is via a .for method (or whatever method you deem is an appropriate shim). Example:

    Demo = Struct.new :one, :two do
      def self.for(...) = new(...)
    ap Demo.method(:for).parameters
    #   [
    #     :rest,
    #     :*
    #   ],
    #   [
    #     :keyrest,
    #     :**
    #   ],
    #   [
    #     :block,
    #     :&
    #   ]
    

    Notice how we get better parameter information — well, minus the block parameter — which tells the truth in terms of being able to use positional or keyword parameters. To take this a step further by being specific, use splats instead. Example:

    # Positional Only. Parameters: `[[:rest, :*]]`.
    Demo = Struct.new :one, :two do
      def self.for(*) = new(*)
    # Keyword Only. Parameters: `[[:keyrest, :**]]`.
    Demo = Struct.new :one, :two do
      def self.for(**) = new(**)
    

    In all of these examples, you now have more accurate parameter information via .for instead of .new which comes in handy when dynamically messaging these objects and using a gem like Marameters, for example.

    Procs and Lambdas

    Starting with Ruby 3.2.0, support was added for proc parameters to be converted into lambda parameters for quick proc to lambda conversion. Example:

    demo = proc { |one, two = 2| [one, two] }
    demo.parameters               # [[:opt, :one], [:opt, :two]]
    demo.parameters lambda: true  # [[:req, :one], [:opt, :two]]

    Notice, by passing lambda: true to the #parameters method, the proc will answer back parameters as if the proc was a lambda since the first parameter would be required for a lambda but optional for a proc. This provides a way for you to dynamically convert a proc into a lambda.

    One caveat to be aware of with procs/lambas is: argument forwarding. Currently, argument forwarding isn’t possible. Consider the following:

    inspector = lambda do |*positionals, **keywords, &block|
      puts "Positionals: {#positionals}, Keywords: #{keywords}, Block: #{block}"
    # Syntax Error
    proc { |...| inspector.call(...) }
    # unexpected (..., expecting '|'
    # Syntax Error
    proc { |*, **, &| inspector.call(*, **, &) }
    # no anonymous rest parameter
    # no anonymous keyword rest parameter
    # no anonymous block parameter
    # Named parameters are OK to be forwarded.
    demo = proc do |*positionals, **keywords, &block|
      inspector.call(*positionals, **keywords, &block)
    demo.call(1, a: 1) { "example" }
    # Positionals: {#positionals}, Keywords: {:a=>1}, Block: #<Proc:0x00000001191108b8 /demo:105>

    Anonymous argument forwarding might be supported in the future so keep an eye on future versions of Ruby to see how this evolves.

    module Operation
      def self.demo one, two = :b, *three, four:, five: :e, **six, &seven
        puts <<~ARGUMENTS
          1 (reg):      #{one.inspect}
          2 (opt):      #{two.inspect}
          3 (rest):     #{three.inspect}
          4 (keyreq):   #{four.inspect}
          5 (key):      #{five.inspect}
          6 (keyrest):  #{six.inspect}
          7 (block):    #{seven.inspect}
        ARGUMENTS
    

    Keep this implementation in mind or scroll back, when needed, to reread since this implementation will be referenced repeatedly.

    Basic

    With the above implementation, the minimal set of arguments we could use are:

    Operation.demo :a, four: :d
    # 1 (reg):      :a
    # 2 (opt):      :b
    # 3 (rest):     []
    # 4 (keyreq):   :d
    # 5 (key):      :e
    # 6 (keyrest):  {}
    # 7 (block):    nil

    All seven arguments — a few of which are either nil or empty since they are optional — are printed to the console. This output can be further explained as follows:

    opt: b: This positional argument was optional so the default value of :b was answered back.

    rest: []: This positional argument was optional too but since we gave it nothing, we got an empty array due to single splats always resolving to an array.

    keyreq: d: This keyword argument was required so we had to pass in a value (i.e. :d).

    key: e: This keyword argument was optional so the default value of :e was answered back.

    keyrest: {}: This keyword argument was optional too but since we gave it nothing, we got an empty hash due to double splats always resolving to a hash.

    block: nil: We didn’t supply a block so we got back nil in this case since blocks are optional since they can be implicitly or explicitly used.

    function = proc { "test" }
    Operation.demo :a, :b, :y, :z, four: :d, y: 10, z: 20, &function
    # 1 (reg):      :a
    # 2 (opt):      :b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   :d
    # 5 (key):      :e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x0000000109679ad0 /snippet:32>

    This time we have a lot more arguments passed in and printed out to the console. We can break this down further highlighting the differences:

    rest: [:y, :z]: Any number of optional positional arguments could have been supplied here but only :y and :z were used in this case.

    keyreq: d

    key: e

    keyrest: {:y ⇒ 10, :z ⇒ 20}: Any number of optional keyword arguments could have been supplied here but only y: 10 and z: 20 were used in this case.

    block: #<Proc:0x000000010d1cbc78>: Since we passed in an explicit block, you can see it’s pass through and printed out as well.

    Single (array): Evaluates to *positionals.to_a. This behavior is definitely surprising and an oddity because you’d expect the implicit #to_ary to be called instead of the explicit #to_a which is inconsistent with how double and block arguments work.

    Double (hash): Evaluates to **keywords.to_hash.

    Block (proc): Evaluates to &block.to_proc.

    💡 For more information on proper use of implicit and explicit casting, see my Ruby Antipatterns articles for details.

    You want to splat your arguments when you need to destruct your arrays, hashes, or proc into a list of arguments or can’t directly pass them to a method but need to dynamically build up the arguments instead. With the .demo method implementation from earlier — and using our knowledge of positional, keyword, and block parameters — we can pass single and double splats along with a block to this method as follows:

    Operation.demo *%i[a b y z], **{four: :d, y: 10, z: 20}, &function
    # 1 (reg):      :a
    # 2 (opt):      :b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   :d
    # 5 (key):      :e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x0000000119418d58 /snippet:32>

    Notice the above’s output is identical to our earlier example (except for the new Proc instance) where we passed in a maximum set of arguments. What’s different is we’ve categorized the positional, keyword, and block arguments. Single and double splats makes this easier. To take this a step further — and assuming the argument list was dynamically assigned to local variables — the code then becomes more descriptive:

    function = proc { "test" }
    positionals = %i[a b y z]
    keywords = {four: :d, y: 10, z: 20}
    Operation.demo *positionals, **keywords, &function
    # 1 (reg):      :a
    # 2 (opt):      :b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   :d
    # 5 (key):      :e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x0000000109c59810 /snippet:32>

    Keep in mind that single and double splats must be used when destructuring arrays and hashes for messaging purposes. For example, the following doesn’t work because you have to explicitly use single or double splats with your arguments:

    Operation.demo positionals, keywords, &function
    #  missing keyword: :four (ArgumentError)

    We’ll learn more about splats in message delegation next.

    Super

    The super keyword is one of several Ruby Keywords that allows you to forward arguments to the parent method when using inheritance. The super keyword deserves special mention since behavior can differ based on the types of arguments used. Consider this basic implementation:

    class Parent
      def demo(*positionals, **keywords, &block)
        puts "Positionals: #{positionals}, Keywords: #{keywords}, Block: #{block}"
    class Child < Parent
      def demo(*positionals, **keywords, &block) = super
    child = Child.new
    child.demo(1, b: 2) { :example }
    # Positionals: [1], Keywords: {:b=>2}, Block: #<Proc:0x000000013e9d9ba0>

    The above shouldn’t be surprising when forwarding arguments from the child to the parent because super — without explicit arguments or parenthesis — implicitly forwards the same arguments. Same goes if you modify the arguments before calling super:

    class Child < Parent
      def demo(*positionals, **keywords, &block)
        positionals.append :a
        keywords[:b] = 2
        super
    Child.new.demo { :example }
    # Positionals: [:a], Keywords: {:b=>2}, Block: #<Proc:0x0000000133c190b0>

    However, using parenthesis, will prevent super from forwarding any arguments:

    class Child < Parent
      def demo(*positionals, **keywords, &block) = super()
    child = Child.new
    child.demo(1, b: 2) { :example }
    # Positionals: [], Keywords: {}, Block: #<Proc:0x0000000148034208>

    As you can see, the positional and keyword arguments were not forwarded but notice the block was! This is because super implicitly forwards the block whether you like it or not. To prevent this behavior, use super(&nil):

    class Child < Parent
      def demo(*positionals, **keywords, &block) = super(&nil)
    child = Child.new
    child.demo(1, b: 2) { :example }
    # Positionals: [], Keywords: {}, Block:
    # frozen_string_literal: true # Save as `snippet`, then `chmod 755 snippet`, and run as `./snippet`. require "bundler/inline" gemfile true do source "https://rubygems.org" gem "amazing_print" gem "debug" gem "dry-monads" gem "marameters" include Dry::Monads[:result] function = proc { "test" } module Operation def self.demo one, two = :b, *three, four:, five: :e, **six, &seven puts <<~ARGUMENTS 1 (reg): #{one.inspect} 2 (opt): #{two.inspect} 3 (rest): #{three.inspect} 4 (keyreq): #{four.inspect} 5 (key): #{five.inspect} 6 (keyrest): #{six.inspect} 7 (block): #{seven.inspect} ARGUMENTS class Exampler def initialize operation, method, marameters: Marameters @operation = operation @method = method @marameters = marameters def first_example(...) operation.public_send(method, ...) def second_example(*, **, &) operation.public_send(method, *, **, &) def third_example *positionals, **keywords, &block operation.public_send method, *positionals, **keywords, &block def fourth_example arguments positionals, keywords, block = arguments operation.public_send method, *positionals, **keywords, &block def fifth_example result result.fmap do |positionals, keywords, block| operation.public_send method, *positionals, **keywords, &block def sixth_example arguments marameters.categorize(operation.method(method).parameters, arguments) .then do |splat| operation.public_send method, *splat.positionals, **splat.keywords, &splat.block private attr_reader :operation, :method, :marameters

    The major changes are that:

    Monads are used. Don’t worry about needing to know how to use monads. You only need to care about the destructuring of arguments in the #fifth_example method. I’ll explain, shortly.

    The Marameters gem is introduced as well which provides a convenient way to obtain method parameter information. We’ll talk about this gem in the final example.

    Now we can focus on the #*_example methods in terms of argument forwarding, splats, and destructured arguments.

    Argument Forwarding

    Argument forwarding (i.e. ...) makes passing of arguments less of a hassle in some cases. To see argument forwarding in action, here’s how make use of the above code snippet:

    exampler = Exampler.new Operation, :demo
    exampler.first_example :a, :b, :y, :z, four: :d, y: 10, z: 20, &function
    # 1 (reg):      :a
    # 2 (opt):      :b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   :d
    # 5 (key):      :e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x0000000109675980 /snippet:32>

    By now, the output should come as no surprise since all we’ve done is forward all arguments from the #first_example method to the .demo method by adding a leading argument to specify the :demo method when using #public_send. This is a elegant and the most succinct of all examples.

    Splats

    Should argument forwarding not be desired or needed, we can always fall back to using splats as provided by the second and third examples. We’ll start with the second example:

    exampler = Exampler.new Operation, :demo
    exampler.second_example :a, :b, :y, :z, four: :d, y: 10, z: 20, &function
    # 1 (reg):      a
    # 2 (opt):      b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   d
    # 5 (key):      e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x000000010b7cbee0 /snippet:18>

    As you can see from the output, there is no change between the first and second example. What might be surprising is you can use anonymous single splat, double splat, and block parameters. This is new in Ruby 3.2.0. If we want to be more verbose, we can name our arguments as done in the third example:

    exampler = Exampler.new Operation, :demo
    exampler.third_example :a, :b, :y, :z, four: :d, y: 10, z: 20, &function

    Again, no change in output as we get the same desired result but this time we’ve given our single splat, double splat, and block arguments a name.

    A subtle distinction is that you must use splats in your parameters and continue to use splats when delegating the arguments, otherwise you’ll end up with nested arrays and/or hashes. Here are a few examples of the errors you’ll get when the code is modified without splats:

    # No parameter splats.
    def third_example positionals, keywords, &block
      operation.public_send method, *positionals, **keywords, &block
    # Yields:  wrong number of arguments (given 5, expected 2) (ArgumentError)
    # No argument splats.
    def third_example *positionals, **keywords, &block
      operation.public_send method, positionals, keywords, &block
    # Yields: `demo': missing keyword: :four (ArgumentError)
    exampler = Exampler.new Operation, :demo
    exampler.fourth_example [%i[a b y z], {four: :d, five: :e, y: 10, z: 20}, function]
    # 1 (reg):      :a
    # 2 (opt):      :b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   :d
    # 5 (key):      :e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x00000001091916a8 /snippet:32>

    In this case, the single array argument is composed of positional, keyword, and block elements which are destructured into positionals, keywords, block local variables so they can be passed on using a single splat, double splat, and ampersand. There is no way to dynamically use *, **, or & unless you evaluate the expression as a string. Example:

    def fourth_example arguments
      positionals, keywords, block = arguments
      instance_eval "operation.#{method} *positionals, **keywords, &block", __FILE__, __LINE__
    

    Instance evaluation would be an unnecessary — and less performant — so prefixing the local variables with *, **, and & is better. A similar pattern is used when passing a monad as an argument to the fourth method:

    exampler = Exampler.new Operation, :demo
    exampler.fifth_example Success([%i[a b y z], {four: :d, five: :e, y: 10, z: 20}, function])
    # 1 (reg):      a
    # 2 (opt):      b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   d
    # 5 (key):      e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x0000000109ba35b0 /snippet:18>

    The key difference is that we can use the block parameters to automatically destructure into positional, keyword, and block arguments needed for message passing. 🎉

    Sadly, we can’t use argument forwarding in block parameters. It would be nice but is currently not possible.

    Marameters

    At the start of this article, I alluded to the Marameters gem. Specifically, the .categorize method used in the #sixth_example method. Using everything we’ve discussed above, Marameters will ensure the arguments align as required by the method’s parameters. Example:

    exampler = Exampler.new Operation, :demo
    exampler.sixth_example [:a, :b, %i[y z], {four: :d}, nil, {y: 10, z: 20}, function]
    # 1 (reg):      :a
    # 2 (opt):      :b
    # 3 (rest):     [:y, :z]
    # 4 (keyreq):   :d
    # 5 (key):      :e
    # 6 (keyrest):  {:y=>10, :z=>20}
    # 7 (block):    #<Proc:0x0000000109370b40 /snippet:32>

    As you can see, the output is the same as all of our earlier examples. No change and no surprise. However — and this is important to be compatible with Method#parameters — we use a single array argument which means all elements of the array must be in the right position to match the equivalent positions of the method’s parameters.

    The Marameters gem can be handy when you want to build an array of arguments for forwarding to a method. Definitely check out the gem’s documentation for more details.

    During the course of this article we’ve learned how to parse a methods’s parameters, along with the kinds of parameters you can use in a method signature, and how the arguments passed to the method are parsed based on the defined parameters. We’ve also learned, briefly, how to leverage the Marameters gem to dynamically build a list of method arguments. Hopefully, with this knowledge, you’ll be able to leverage better use of parameters and arguments in your own implementations too. Enjoy!