Talks
Ruby keyword args and the options hash, from the parser to the virtual machine

Summarized using AI

Ruby keyword args and the options hash, from the parser to the virtual machine

Étienne Barrié • October 01, 2015 • Ghent, Belgium

In the talk titled "Ruby keyword args and the options hash, from the parser to the virtual machine" presented by Étienne Barrié at ArrrrCamp 2015, the speaker delves into the evolution and utilization of keyword arguments in Ruby. The presentation begins by establishing that keyword arguments were introduced gradually into Ruby, starting from the basic options hash to the more sophisticated support in later versions.

Key points discussed include:
- Definition of Keyword Arguments: Keyword arguments allow function calls to explicitly state the name of each parameter, enhancing readability and reducing confusion when passing multiple arguments.
- Advantages of Keyword Arguments:
- They make code more readable and maintainable by eliminating the need to remember the order of parameters.
- Easier to manage changes in method definitions, particularly when adding or removing parameters.
- Positional vs. Keyword Arguments: Barrié compares positional arguments, which can lead to confusion especially in method calls spread across different files, with keyword arguments that provide clarity.
- Options Hash: Before keyword arguments were officially introduced, Ruby developers often used an options hash to simulate keyword arguments, allowing for more descriptive and flexible method calls.
- Evolution of Keyword Arguments in Ruby:
- Over time, Ruby incorporated directives that simplify the usage of keyword arguments, such as optional keyword arguments and the double splat operator to handle remaining arguments.
- The introduction of required keyword arguments in Ruby 2.1 was highlighted as a significant upgrade, improving both functionality and developer experience.
- Implementation Details: The talk touches on the complexity of Ruby's handling of keyword arguments and highlights some potential performance pitfalls compared to traditional method calling.
- Demonstrations of Errors: Practical examples are provided to illustrate common pitfalls when using keyword arguments, emphasizing the need for careful coding practices to avoid typical mistakes, especially with empty hashes and typos.

Through the presentation, Barrié argues that while keyword arguments improve efficiency in coding, they fundamentally do not exist in Ruby in the same way that they might in other programming languages such as Python. The conclusion rests on the idea that the semantics of keyword arguments could still be refined further in future Ruby versions, enhancing their utility without the need for cumbersome workarounds.

Overall, the talk emphasizes the tradeoffs associated with keyword arguments and the importance of understanding their implementation in Ruby's architecture, ultimately promoting their usage while acknowledging their limitations and quirks.

Ruby keyword args and the options hash, from the parser to the virtual machine
Étienne Barrié • October 01, 2015 • Ghent, Belgium

By Étienne Barrié
Ruby has slowly but surely added support for keyword arguments. Starting from the implicit braces for a hash at the end of an argument list, it has grown up to required keyword arguments in 2.1. This talk will try to convince you that keyword arguments are a lie and don't even exist, and why you should use them anyway.

Help us caption & translate this video!

http://amara.org/v/H4Od/

ArrrrCamp 2015

00:00:07.950 Hello! Today I'm going to talk to you about keyword arguments in Ruby. We'll see why they're useful, how they were introduced gradually in Ruby over the versions, and how it all starts with the options hash.
00:00:10.990 You may already have a good idea of what keyword arguments are, but let's start with the definition. Wikipedia states that keyword arguments refer to a programming language's support for function calls that clearly state the name of each parameter within the function call itself.
00:00:18.369 In Ruby, when we call a method, we name each parameter as we pass it an argument. Let's first look at the advantages this brings over regular parameters. When we define a method in Ruby, we give it an ordered list of parameters. This means that later, when we're going to call the method, the position of the arguments will matter, which is why they are called positional arguments.
00:00:54.280 When you look at the method call itself, there’s no way to guess which arguments correspond to which parameters. If the method call is in a different file than the method definition, which happens often, this can lead to confusion. If we examine the definition of a method with keyword arguments, we see that it's very similar.
00:01:20.590 However, when we read the method call now, it’s much clearer what we’re going to get. Also, the order of the arguments doesn’t matter. So, if you're a bit particular about order, like me, you can arrange them alphabetically. We don't need to know the order of the parameters anymore.
00:01:48.490 Of course, we need to know the names now, and we need to type them. They take up space on our screens, but it’s more verbose this way. However, I would argue it’s also more maintainable because it allows you to change things more easily.
00:02:05.289 The only thing we can be certain of is that there will be changes later on. Thus, it’s easier to add new parameters or replace existing ones. For example, if you want to support percentage taxes, it will be much easier than using positional arguments.
00:02:45.220 This phenomenon can be referred to as coupling—basically a design pattern in object-oriented programming. It means that if two things are coupled, changing one will necessitate changes in the other, or at least you might need to check the corresponding call sites. In our case, if we change the positional method definition, we will likely need to modify the method calls in other files.
00:03:15.760 So, I won’t spend more time on coupling, but if you want to learn more about it, there are great resources available. At this point, we might think it would be a good idea to use keyword arguments everywhere. But, obviously, there’s always a trade-off.
00:03:50.480 If you have a method that only takes one parameter—or even two parameters if they’re obvious—using keyword arguments won’t provide useful clarity. Instead, it can make your code more verbose and, potentially, more confusing. For example, in complex numbers, the definitions I've read typically specify the real part followed by the imaginary part; this is clear enough and doesn’t require naming them.
00:04:26.110 The same principle applies for rational numbers, which are defined by the quotient and the denominator. Similarly, when defining methods that take a number of elements in a collection, it’s not necessary—let alone helpful—to name the parameter 'n' or 'count.' In those cases, it just increases verbosity.
00:04:57.740 Interestingly, you might often want to use literals for imaginary and rational numbers, so it’s best to keep things simple. Now, the first thing you should know about named parameters in any programming language is that you can emulate them using any data structure, such as an associative array like a struct in C, an object in JavaScript, or a hash in Ruby.
00:05:50.500 And it makes sense because an associative array allows us to associate a name with a value, which is similar to what we want to achieve with keyword arguments. You can think of this as comparing a two-dimensional array to a simple one-dimensional array that reflects a regular parameter list.
00:06:00.960 For a long time in Ruby, we’ve used what we call an options hash, particularly for optional parameters. By passing a hash, we effectively name the parameter, and we can also provide the value directly. The great thing we all know and love in Ruby is that we don’t need the implicit hash—we can omit the curly braces, which looks pretty great.
00:06:25.960 In fact, while doing research for this talk, I found a reference in the first edition of Programming Ruby, which came out when Ruby 1.6 did not support keyword arguments. It even mentioned the Ruby schedule, which made me giggle. Now, moving back to our example, if we wanted to add a percentage tax feature, we could simply add default values.
00:06:56.070 However, the problem arises when the full hash is treated as a single parameter, preventing us from acquiring the default tax rate value. Over the years, several techniques emerged to get around this with boilerplate code, and various libraries provided ways to express this more clearly.
00:07:28.490 For example, Active Support offers reverse merge, which makes the code easier to read at a glance. Nonetheless, it’s still something we would prefer not to deal with. The options hash has become a popular pattern within Ruby, and we use it often. In fact, if we look at Rails, in the latest version, it has been used almost 500 times throughout the entire codebase.
00:08:02.789 In Shopify, it’s even more widespread! So, let’s return to keyword arguments and explore how we can trick Ruby into using the options hash as keyword arguments. One straightforward method involves simply using the values.
00:08:34.690 Using hash indexing can seem appealing, but it lacks clarity. What could possibly go wrong here? For instance, let's say we forget an argument; it leads to a 'no method' error. In such cases, it’s quite easy to see the issue, but in other cases, it can become buried within a deeper stack trace.
00:09:06.540 What we've learned, especially from instructors like Fred George, is that we should be using fetching for parameters to clarify that these keys are required. Yet, even then, things can go wrong. For instance, we all have those mornings when we're coding before our first cup of coffee, leading to unexpected results stemming from typos.
00:09:45.450 To help catch these potential issues, Active Support provides a method called 'valid keys,' which assists in validating the expected keys. Another issue arises when we try to combine the use of the splat operator with options hashes.
00:10:26.289 This situation requires additional boilerplate code to handle the merging properly. Throughout this talk, I’ve come across multiple instances where developers thought they were just one line of code away from a solution only to find it was going to take much more.
00:10:55.790 Indeed, in Rails, they implemented extractable options to function exclusively with instances of hash. Over time, they’ve had to redefine it for other subclasses, allowing those specific classes to utilize extractable options, primarily due to issues with overlapping definitions.
00:11:18.129 One of the problems that developers hope to address concerns the parsing. We wanted this functionality to seamlessly integrate into Ruby, creating a consistent behavior for everyone. Returning back to Ruby 2.0, we used the newer hash syntax with a feature that is visually appealing.
00:11:50.250 It's much nicer around the eyes, yet it did not change the methodology of the method definition itself—only the method call changed. This syntax works exclusively when the keys are symbols, but typically that’s what we desire.
00:12:22.860 However, sometimes this is not the case. Ruby 2.0 introduced optional keyword arguments, which was a monumental improvement for us. We could remove a considerable amount of boilerplate code, ensuring that all optional parameters were defined, reducing the likelihood of errors due to typos.
00:12:53.610 With Ruby 2.0, the double splat operator was introduced, collecting all remaining keyword arguments into a hash, reverting us to a previous state. Yet, we still needed to use fetch to prevent careless mistakes. Ultimately, Ruby 2.0 led to the introduction of required keyword arguments.
00:13:27.190 This addition was met with great enthusiasm because we were finally able to use keyword arguments seamlessly. Initially, I experienced some confusion regarding behavior when I first attempted to implement keyword arguments, which felt somewhat odd, almost like a bug. In scenarios where a method takes no parameters, calling it without providing keyword arguments works.
00:14:02.329 However, if we pass no keyword arguments at all, the resulting behavior can be unexpected.
00:14:35.950 This inconsistency persisted until Ruby 2.2, which streamlined the process, allowing it to function correctly. There was a crash in the parser during development, but thankfully, that crash was never deployed and was identified. The fix introduced a peculiar side effect where it now ignores empty literal hashes.
00:15:14.310 Nonetheless, you can still achieve unexpected behaviors just by passing a value. The core issue here underlines my argument that keyword parameters don't truly exist in Ruby; they aren’t regular parameters available for access and manipulation by name.
00:15:46.469 Despite keyword arguments improving significantly over the years, they still don’t feel entirely material. To illustrate, let's compare Ruby with another language like Python, which has a more comprehensive implementation of keyword arguments.
00:16:19.919 In Python, any parameter can be named without needing a special definition. The parameters inherently possess both name and position, allowing you to pass any number of parameters positionally until you choose to pass them by name.
00:16:54.179 This applies even to optional parameters, which can still be passed on just like how we do in Ruby. Revisiting the Wikipedia definition, we remember it references function calls. However, in Ruby, we’d prefer to say we’re sending a message.
00:17:22.999 When we send a message in Ruby, we pass arguments and potentially a block of code. Moreover, the arguments are just an array—we don't actually pass keyword documents.
00:17:54.880 It turns out that arguments are similar enough to an array that we can utilize the same syntax when sending messages. The simplest example involves the splat operator, which allows for a similar context format. Notably, we can also use the hash collection feature with an array literal in the same way.
00:18:23.860 This is why I argue that when sending messages, we are essentially sending an array of arguments, and the method call syntax merely employs array literal syntax. At this point, we revisit the use cases of passing any number of arguments, leveraging options and the splat operator.
00:18:56.700 The implementation of the double splat operator in Ruby 2.0 had some oddities, where the placement of the double splat would affect the behavior in unexpected ways. Ultimately, within Ruby 2.1, the method calls and definitions started having clearer defaults.
00:19:34.050 If we examine the usage of double splatted hashes, the precedence rules dictate that external hash values take priority over those within the double splat.
00:19:57.560 Going forward, if we want to override a literal key with an option hash, we can achieve this by splitting the hashes after the original method call.
00:20:07.200 Ruby 2.2 allows us to compactly send merged hashes. This brings us to the implicit conversion, which is one of Ruby's exemplary features.
00:20:42.720 Duck typing is applied effectively here, as you may remember with the symbol to proc method. However, if you pass any object and attempt a double splat, Ruby will block this process if an implicit conversion isn't defined. The longer name methods present here can lead to confusing behaviors as well.
00:21:18.900 The implicit conversion to hash may be overly generic and conflicts can arise when swift methods are used for merging without realizing the expectations of key types within the double splat context.
00:21:55.900 To conclude, what remains open for discussion is the best approach for explicit conversions since we are already writing conversion routines within methods to get values from objects directly.
00:22:24.780 Now let's shift our focus to performance. Although it’s not the paramount concern, it does carry weight in Ruby 2.1. Calling methods with keyword arguments can be notably slower than expected due to its internal handling.
00:22:59.349 For instance, executing 10 million method calls on a simple hash may take around one second compared to the significantly slower performance of keyword arguments. However, Ruby eventually improved the performance by enhancing how method definitions interact with the virtual machine.
00:23:37.809 You can see this process by checking out the generated bytecode from a Ruby method call. In this context, the first step involves defining the method. The second part consists of the compiled method output.
00:24:10.280 The internals of method traces allow Ruby to comprehend method entry and exit, which greatly assists in performance metrics.
00:24:41.739 By utilizing bytecode analysis and the virtual machine, keyword arguments' performance improved significantly in Ruby 2.2, where the arguments would be stacked as standard parameters and the method effectively behaved as we would anticipate.
00:25:09.020 In this setup, the method would naturally declare the keywords rather than checking for their existence during each call.
00:25:42.780 Ruby 2.2 also improved the hash syntax by allowing keys that wouldn’t traditionally be identifiers, enabling interoperability between JSON structures and Ruby hashes.
00:26:19.200 Indeed, JSON structures now work seamlessly in IRB when properly defined, which leads to more efficient and readable code.
00:26:45.070 Nonetheless, a repeated concern emerges when dealing with keyword arguments: they can become verbose. In many cases, like in factory methods, one could potentially simplify code by passing a varying number of keyword arguments, which would seamlessly relay to the new method implementation.
00:27:09.950 As keyword arguments proliferate, their redundancy becomes noticeable, leading to contemplation of improved syntactic aids akin to ES6 syntax, allowing for more compact argument usage.
00:27:34.630 Typically, upon returning to the original issues with keyword arguments in Ruby, we can illustrate the issues faced. When observing instances where no keyword arguments are passed for dispatching, one risks failure due to methods not designed to handle parameters.
00:28:09.040 Currently, the workaround involves using boilerplate code to avoid pushing empty hashes into methods that can’t accept them. In conclusion, it’s apparent that many challenges associated with keyword arguments have already been addressed collaboratively by the Ruby core team and the community.
00:28:48.080 Moving forward, we can ensure that keyword arguments always operate fruitfully within the Ruby framework—and perhaps evolving the semantics around message passing could prove beneficial. Feel free to connect with me at a Ruby event or via Shopify, and I am happy to support with any questions you might have.
Explore all talks recorded at ArrrrCamp 2015
+14