Soutaro Matsumoto

Embedding it into Ruby Code

RubyKaigi 2024

00:00:09.719 Okay.
00:00:10.840 Hello, I’m Soutaro Matsumoto, and I will be presenting.
00:00:15.679 I think the title is a bit confusing, especially for this year. I'm afraid that some of you might be expecting a presentation about the new syntax in Ruby 3.2, but that’s not what this is about.
00:00:26.240 The discussion will be centered around writing your RBS type declarations inside the Ruby code.
00:00:34.360 I want to start with a quick demonstration. This code is a simple example with no type definitions. As you can see, there is no definition for the Talk class, which causes an error saying that it cannot find the declaration of the constant Talk.
00:01:02.640 Let’s start by adding the class definition. Once the type definition is updated, the Talk constant is detected, but it doesn't have the initialize method. So, the default new method is used, which accepts no arguments.
00:01:24.640 The type checker reports an issue regarding the arguments. We can correct this by adding the initialize method with three arguments.
00:01:36.399 Then, we can write the types of variables using the RBS syntax. This adjustment changes the types of the method arguments. For instance, we can specify that the first argument of the initialize method is a String, and we can add type annotations for the other parameters.
00:02:01.039 To clarify, the second parameter is a Date, and the third is an array of Strings. However, currently, we are passing a String instead of an array, which needs to be addressed.
00:02:42.800 Next, we need to add some attributes. We use the double colon syntax to define the type of the attributes, along with a few additional attributes.
00:03:11.200 We can then implement the initialize method, which assigns the title to the @title instance variable.
00:03:30.679 Today’s presentation is about how you can incorporate RBS type declarations inside your Ruby code. To introduce myself further, I’m Matsumoto, one of the Ruby contributors, and I’ve been working on the RBS type definitions since April.
00:04:02.120 Many of you likely attended the sponsor presentation this morning, so you may already be familiar with my work.
00:04:37.919 This is how we use type checking. We need to maintain two sets of files: Ruby code files that include the implementation, and RBS files that contain the type definitions of your program. On the left is the Ruby code, while on the right is the RBS type definition.
00:05:12.280 The type checker will verify the consistency between the two. As you can see, in this instance, the method ‘speakers’ has an underline indicating that there’s a type error.
00:05:50.000 The type definition for ‘speakers’ indicates it always returns a Speaker object, but it calls an array method that returns nil when the speakers array is empty.
00:06:28.440 This design choice of separating the type definition files from implementation files isn’t common in today’s modern statically typed languages like Java or Swift.
00:06:50.720 In such languages, you typically don’t write type definitions in separate files like header files in C or C++. However, we chose to do it this way in Ruby for specific reasons.
00:07:15.240 One primary reason is to avoid extending Ruby syntax for type annotations. We decided against modifying the Ruby syntax to allow embedding type information in the code.
00:07:59.760 Additionally, there were technical concerns regarding the implications of adding new syntax without breaking compatibility, as Ruby’s syntax can be quite complicated.
00:08:38.200 If we want to avoid extending Ruby syntax, we could merely place types in string remarks. However, I wanted to avoid this because string remarks are frequently used in Ruby code to represent actual data.
00:09:10.080 I don’t want to mix static type information with runtime data that’s essential for program execution.
00:09:35.440 The best approach I found was through metaprogramming. This metaprogramming allows defining Ruby programs without specific syntaxes, like class or def syntax.
00:10:11.760 For example, it’s possible to create a class without using the conventional class syntax.
00:10:42.080 But this can make it quite challenging to track the structure of the Ruby code due to the complexity of metaprogramming.
00:11:10.440 Another problem arises when there is no class syntax or def syntax in Ruby code, which leads to challenges in declaring their types.
00:11:37.920 To address this, I decided to put type information in a separate file because it is not actual code, allowing us to define a concise and clear syntax without worrying about potential conflicts.
00:12:15.679 This allows for a shift in semantics, as we can keep everything static within the RBS files without the need for metadata or method definitions using Ruby syntax.
00:12:52.800 Yet, there are benefits to having separate files for type definitions.
00:13:34.200 The RBS files included in many of my gems serve to clarify the structure of the Ruby code, making it easy to read. There are clear modules, attributes, and methods, allowing one to focus more on the program’s overall outline rather than implementation details.
00:14:35.600 We need to declare the types of expressions; if we can implement a good mechanism to embed type declarations in Ruby, we can simplify the coding process.
00:15:16.240 However, this cannot be applied to classes defined in extensions, which have a different syntax as they are written in C or other languages.
00:15:55.359 Thus, these are some reasons for introducing RBS files for type checking, though there are some challenges involved.
00:16:20.520 The most obvious difficulty is that switching between files during development can be cumbersome. You need to jump between Ruby source files and RBS files.
00:17:26.760 This requires maintaining two sets of files, which can be tedious and lead to synchronization issues.
00:18:09.440 For example, when starting development on a class, you’ll write the Ruby code and the RBS file. However, the real issue arises when trying to delete a method; you must remove it from both the type definition and the implementation.
00:19:39.120 This can lead to mistakes where one might forget to delete one of the entries, creating unnecessary clutter in the codebase.
00:20:01.120 Additionally, while reviewing pull requests, we require type information to effectively evaluate the changes.
00:20:22.560 If a pull request only modifies the method signatures, we may miss this information due to the lack of visibility into the definitions.
00:20:53.599 Having the type of the ‘command location’ method included would greatly assist in catching potential issues.
00:21:24.160 I wanted to stress that type checking in Ruby is an optional feature; you can choose to ignore it entirely if you wish.
00:21:58.200 However, I believe there are advantages to utilizing the types. With the increasing adoption of RBS, I want to encourage everyone to adopt types more vigorously.
00:22:35.040 Stronger connections between the code and types will enhance overall code quality.
00:23:14.160 I released the RBS inline gem, which allows you to write type definitions directly in your Ruby code.
00:24:04.560 This gem enables you to embed RBS type declarations as comments within your Ruby code, making it convenient.
00:24:40.000 The comments are processed by the RBS inline tool, which reads the code to detect type annotations and generates corresponding RBS files.
00:25:22.679 Using this gem means you don’t have to maintain two sets of files; the focus can remain on editing and reading your Ruby code.
00:26:04.960 The RBS inline gem extracts embedded type definitions into RBS files, allowing you to validate the correctness of both implementations and type definitions.
00:26:46.760 The RBS syntax supports various constructs and can provide annotations on parameters, return types, and more, making type definitions more manageable.
00:27:29.520 We continue to explore better ways to implement RBS inline syntax. Feedback has been incredibly valuable as we work towards a more refined solution.
00:28:09.160 I invite anyone interested to join the discussions on GitHub regarding feature requests and potential improvements.
00:28:48.480 We aim to embed RBS type declarations directly into Ruby code to enhance the development experience.
00:29:10.440 I will be available for a small meeting with contributors during the afternoon break today, so feel free to join me for further discussions.
00:29:40.305 Thank you.