Today I Learned Notes to self about software development

    Ruby Keyword Arguments

    I rarely define methods that use them so I often forget. There are a few different ways to define keyword arguments in a method.

    A method can be defined with positional arguments, keyword arguments (which are defined and called using the : syntax) or have no arguments at all.

    class Calculator
      # Positional arguments
      def add(num1, num2)
        return num1 + num2 # Explicit return
      end
    
      # Keyword arguments
      def multiply(num1:, num2:)
        num1 * num2 # Implicit return
      end
    end
    

    You can also now pass the arguments in whatever order with keyword arguments.

    c = Calculator.new
    c.multiply(num2: 3, num1: 9)
    # => 27
    

    Which brings us to first way to define keyword arguments, by adding the : to the end.

    Approach #1

    def hello(message:)
      puts "hello " + message
    end
    
    hello # => ArgumentError: missing keyword: message
    hello(message: 'world') # => 'hello world'
    

    You can also define default values for these keywords like this:

    def hello(message: "everyone")
      puts "hello " + message
    end
    
    hello # => 'hello everyone'
    hello(message: 'world') # => 'hello everyone'
    

    Just having the : at the end with no default value always looked incorrect to me (which is probably why I forget about it), since it feels pretty rare for Ruby to determine the “type” of something by the ending character instead of the starting character, but it’s probably a hold over from C or something.

    Approach #2

    def grab_bag(**keyword_arguments)
      p keyword_arguments
    end
    
    grab_bag(letter: "m", some_number: 31)
    # => {:letter=>"m", :some_number=>31}
    

    If you want to accept keyword arguments, in principle you should always use def foo(k: default) or def foo(k:) or def foo(**kwargs)

    Ruby 3 Changes

    In Ruby 3.0, you must explicitly specify which arguments are keywords (using the double splat) instead of relying on Ruby’s implicit last-argument-is-a-hash thing.

    In Ruby 2, keyword arguments can be treated as the last positional Hash argument and a last positional Hash argument can be treated as keyword arguments.

    Basically, in situations like this

    def one_keyword(keyword: 1)
      p keyword
    end
    
    hash = { keyword: 42 }
    

    and you want to pass hash as an argument to one_keyword, you can’t do this anymore:

    one_keyword(hash)
    # => wrong number of arguments (given 1, expected 0) (ArgumentError)
    

    In Ruby 3+ this will throw an Argument error. Now, the syntax is to pass the argument with the double splat like this:

    one_keyword(**hash)
    => 42
    

    The other situation when you have a method with both types of arguments:

    def mixed_arguments(positional, **keywords)
      p positional
    end
    

    If positional is supposed to be a Hash, you have to explicitly declare it as such.

    This won’t work:

    mixed_arguments(word: 42)
    

    This will work:

    mixed_arguments({word: 42})
    

    More reading

    #ruby