Passing a Hash to a Function ( *Args ) and Its Meaning

Passing a hash to a function ( *args ) and its meaning

The * is the splat (or asterisk) operator. In the context of a method, it specifies a variable length argument list. In your case, all arguments passed to func will be putting into an array called args. You could also specify specific arguments before a variable-length argument like so:

def func2(arg1, arg2, *other_args)
# ...
end

Let's say we call this method:

func2(1, 2, 3, 4, 5)

If you inspect arg1, arg2 and other_args within func2 now, you will get the following results:

def func2(arg1, arg2, *other_args)
p arg1.inspect # => 1
p arg2.inspect # => 2
p other_args.inspect # => [3, 4, 5]
end

In your case, you seem to be passing a hash as an argument to your func, in which case, args[0] will contain the hash, as you are observing.

Resources:

  • Variable Length Argument List, Asterisk Operator
  • What is the * operator doing

Update based on OP's comments

If you want to pass a Hash as an argument, you should not use the splat operator. Ruby lets you omit brackets, including those that specify a Hash (with a caveat, keep reading), in your method calls. Therefore:

my_func arg1, arg2, :html_arg => value, :html_arg2 => value2

is equivalent to

my_func(arg1, arg2, {:html_arg => value, :html_arg2 => value2})

When Ruby sees the => operator in your argument list, it knows to take the argument as a Hash, even without the explicit {...} notation (note that this only applies if the hash argument is the last one!).

If you want to collect this hash, you don't have to do anything special (though you probably will want to specify an empty hash as the default value in your method definition):

def my_func(arg1, arg2, html_args = {})
# ...
end

does (hash) mean anything as a function parameter in javascript?

The name of a variable is just a hint at what the function expects as input, but there is no real "type hinting" in Javascript that would enforce any such policy.

Hash/object are used interchangeably in Javascript because object members can also be accessed in a fashion that is similar to the syntax of how you would access entries in a hash table in other languages.

hash.w

is equivalent to

hash["w"]

The latter is a syntax common in languages such as Python or Ruby (in fact the class implementing this behavior is called "Hash" in Ruby).

So the word "hash" does not refer to a cryptographic hash or a hash function but rather to the functionality of a hash table.

Objects are often referred to as "Hashes" in Javascript if they are merely a collection of key/value pairs but don't implement any functions, e.g.

hash = {
a: 5,
b: "string",
c: 7
}

opposed to

object = {
member_a: 5,
member_b: "string",
do_this: function() { ... },
do_that: function() { ... }
}

Passing hashes instead of method parameters

Both approaches have their own advantages and disadvantages, when you use an options hash replacing standard arguments you lose clarity in the code defining the method but gain clarity whenever you use the method because of the pseudo-named paramaters created by using an options hash.

My general rule is if you either have a lot of arguments for a method (more than 3 or 4) or lots of optional arguments then use an options hash otherwise use standard arguments. However when using an options hash it is important to always include a comment with the method definition describing the possible arguments.

Pass hash to a function that accepts keyword arguments

You have to convert the keys in your hash to symbols:

class Song
def initialize(*args, **kwargs)
puts "args = #{args.inspect}"
puts "kwargs = #{kwargs.inspect}"
end
end

hash = {"band" => "for King & Country", "song_name" => "Matter"}

Song.new(hash)
# Output:
# args = [{"band"=>"for King & Country", "song_name"=>"Matter"}]
# kwargs = {}

symbolic_hash = hash.map { |k, v| [k.to_sym, v] }.to_h
#=> {:band=>"for King & Country", :song_name=>"Matter"}

Song.new(symbolic_hash)
# Output:
# args = []
# kwargs = {:band=>"for King & Country", :song_name=>"Matter"}

In Rails / Active Support there is Hash#symbolize_keys

Handling a hash as a function argument

Simply use the definition you provided:

def function_name(options = {})
puts options["key1"]
end

Call it with:

function_name "key1" => "value1", "key2" => "value2"

or

function_name({"key1" => "value1", "key2" => "value2"})

Array#extract_options! is simply used with methods that have variable method arguments like this:

def function_name(*args)
puts args.inspect
options = args.extract_options!
puts options["key1"]
puts args.inspect
end

function_name "example", "second argument", "key1" => "value"
# prints
["example", "second argument", { "key1" => "value" }]
value
["example", "second argument"]

Another useful method is Hash#symbolize_keys! which lets you not care about whether you pass in strings or symbols to your function so that you can always access things like this options[:key1].

In Ruby, how can you pass more than one hash to a method, without parentheses?

Mu points out that you can pass multiple hashes without parentheses, if you pass them in as non-literals i.e. as variables.

But other than that, so, for literal hashes,

You do(need parentheses to pass multiple literal hashes), unless you are passing keyword arguments.. You can pass multiple keyword arguments without parentheses.

a keyword argument would be when the parameters of the method include a colon like e.g. def blah x:, y: then you can call with blah y:2,x:3 . Sometimes you have to look at the parameter(s) to see if an argument is a keyword argument, e.g. if you have a method called with abc x:3
then that might be def abc x: in which case you called it with a keyword argument. Or it might be def abc x in which case you called it with a hash, omitting the {}.

When I say keyword argument, I don't mean a hash.. and vice versa, when I say hash I mean not a keyword argument.

When a call is without parentheses, you can only pass one hash and that hash has to be the last argument. And you can skip the {} around it.

note- I'm no expert, but as to a related question of whether a keyword argument is a type of hash, from what I understand, as of writing, pre ruby 3, they are, but there is a proposal for ruby 3 to have 'real' keyword arguments that are distinct from hashes https://bugs.ruby-lang.org/issues/14183

a keyword argument can't be multi-valued.

also, a hash can be automatically converted to a keyword argument. (e.g. if a method is defined with a parameter that is a keyword argument, you can pass a hash in e.g. {x:"a"} , that x being a symbol, and that hash will be converted to a keyword argument x:"a" (that x being a parameter name).

I'll add a point regarding blocks and parentheses, because a block done with {} does look a little bit like a hash though is not a hash. And a block can have some influence on whether parentheses are needed.

If you see abc {}, that {} is a block not a hash, and blocks don't count as an argument. Hence that call works for def abc but not for def abc x where one would get an error related to number of arguments passed.

even when passing a block after some other arguments, there should be no comma before the block, and if a block done with {} follows some arguments, you need parentheses, but if a block done with do .. end follows some arguments, you don't need parentheses

it is suggested that one use parentheses when one has multiple arguments, unless it's a DSL(domain specific language). Do you leave parentheses in or out in Ruby?

How to hash *args **kwargs for function cache?

Here is the technique used in functools.lru_cache():

kwd_mark = object()     # sentinel for separating args from kwargs

def cached_call(*args, **kwargs):
key = args + (kwd_mark,) + tuple(sorted(kwargs.items()))
return cache.get(key)

Note, the above code handles keyword arguments but makes no attempt to handle non-hashable values like lists. Your idea for using the str or a list is a reasonable start. For set objects, you would need to sort the entries first, str(sorted(someset)). Other objects may not have a useful __repr__ or __str__ (i.e. they may show only the object type and location in memory). In summary, handling arbitrary unhashable arguments requires careful consideration of each object type.

How not to mix hash and keywordArgument in a Ruby function?

The tutorial is not saying that using => (AKA "hash-rocket") is bad, it's saying the use of Strings or other objects besides symbols (:foo) uses more memory.

{ :foo => 'bar' }

is the original way of defining a symbol as a key. Compare these two definitions:

{ :foo => 'bar' } # => {:foo=>"bar"}
{ foo: 'bar' } # => {:foo=>"bar"}

We tend to use foo: because we're lazy.

Symbols don't cost as much, but that's a different subject and why can be found with a little searching.

As developers we need to understand the cost of using one algorithm or object versus another. Using Strings or whatever as a key has its time and place and knowing those can save a lot of memory or development time.

Finally, style guides are useful; Read several of them periodically and consider what they're suggesting. The guides are aimed at teams to help them write in a common and consistent manner following good coding practices adapted for Ruby, however they're not cast in stone. Knowing why they're recommended is important, because occasionally we have to ignore or bend the rules, but, when we do, we better be ready to explain why in a code-review.

And, if you're not familiar with Ruby's coding styles, I'd recommend learning about Rubocop, which is a nice tool for checking for consistent and accepted programming style and catching errors.



Related Topics



Leave a reply



Submit