Programming

Ruby programming with types in action

Ruby programming with types in action

by Soutaro Matsumoto

The video titled "Ruby programming with types in action," presented by Soutaro Matsumoto at RubyKaigi 2022, focuses on the RBS (Ruby Signature) system and type checking in Ruby programming. The speaker outlines the significance of RBS in enhancing code readability and maintenance by providing type definitions for Ruby codebases. Matsumoto shares insights on recent developments in RBS, including version 2.7.3, highlighting new features such as declaring the type of self in blocks and the RDoc Praveen project that generates documentation from RBS files.

Key points discussed include:
- Introductions: Matsumoto discusses his background and his role as a maintainer of RBS.
- RBS Features: He outlines the new versions of RBS, emphasizing new capabilities such as batch cross-talk for RBS files and generating RDoc documentation from RBS rather than RB files.
- Benefits of Type Checking: Type definitions substantially help developers understand code, streamline refactoring, and ease the addition of new features while providing API documentation.

Demonstrations were a significant component of the presentation:
- IDE Usage: Matsumoto demonstrates using the IDE effectively with RBS definitions, showcasing hover functionality for method signatures and completion features for different payment methods.
- Steep Setup: He walks through setting up Steep—a type checker for Ruby—illustrating gem setups, generating RBS files, and running type checkers, although he encountered some issues that curtailed part of the demonstration.

He emphasizes a practical approach to integrating type checking into existing code, recommending that developers tackle type definitions incrementally rather than attempting to fix all type errors initially.

Matsumoto argues that:
- New Code vs. Existing Code: It is more beneficial to write new code using type checkers than to type-check existing code, which often results in detecting only minor issues.
- Union Types and Best Practices: He discusses implementing union types versus inheritance and how they can guide the development process while reinforcing best practices in API design.
- Iterative Development: The creation of RBS type definitions is presented as a back-and-forth process between writing types and coding, emphasizing adaptability in development practices.

The session concludes with a reiteration of the value that types bring to Ruby programming, advocating for a strategy that encourages the integrated writing of type definitions alongside coding to cultivate better programming practices.

00:00:00.840 Foreign.
00:00:06.500 Hello everyone! I want to start my presentation by introducing myself.
00:00:13.980 I hope everyone here knows what RBS is; I developed it.
00:00:21.359 I’m also one of the maintainers and I work for a company that was named Square last year.
00:00:28.320 I like the new name because some people were confusing the company I work for with Shopify or Stripe.
00:00:37.559 There are so many similar names in this area, such as Shopify, Stripe, Square, and Sega.
00:00:43.200 Anyway, this year we have five talks related to RBS and type checking, which is very exciting for me.
00:00:50.879 It was a great experience to watch the live streaming from my home while people were discussing RBS.
00:01:03.059 It was very impressive, but there is one thing that makes me unhappy.
00:01:10.320 Most of the talks, three of the five, focused on generating RBS. I know that's an important topic.
00:01:18.119 However, I want to discuss how we can use the type definitions.
00:01:25.020 It's great to hear presentations about using method types in RBS signatures.
00:01:30.200 Yet, I want to focus on using that type information to read or write code.
00:01:36.540 This leads us to type checking.
00:01:42.600 Before going into my talk, I want to quickly introduce the latest version of RBS.
00:01:48.180 The current version is 2.7.3, and it is in pre-release.
00:01:53.460 To install it, you need to use the --pre option.
00:02:01.920 A new feature of version 2.7 is the support for declaring the type of self in blocks.
00:02:08.880 Let's look at two method definitions.
00:02:17.099 In the first method, it receives a block, and we use square brackets to specify the type of self inside the block.
00:02:24.560 In the first method, 'self' is a String within the block.
00:02:27.420 This is especially useful for base classes.
00:02:33.000 In the context of RBS, self as a type is beneficial when we declare the type of the receiver.
00:02:40.620 RBS version 2.5 was released two months ago and includes a batch cross-talk feature.
00:02:46.500 If you specify the --out-dir option, it will scan all of the RBS files in your project and generate RBS files in the specified output directory.
00:02:52.340 Another feature I want to introduce is RDoc Praveen.
00:02:58.080 This is a project supported by Google Summer of Code.
00:03:04.379 I'm mentoring one student who implemented this feature.
00:03:11.159 This will generate RDoc documentation from RBS files instead of RB files.
00:03:16.440 It scans the RBS files and detects the class definitions, module definitions, and method definitions.
00:03:21.840 Then, it creates an RDoc output.
00:03:27.900 Now, I want to discuss the benefits of type checking.
00:03:33.960 When we have a Ruby codebase that's type-checked with RBS, it means we have RBS definitions for each class, method, and module.
00:03:39.060 This will help us understand the code better.
00:03:42.560 The RBS files work as API documentation.
00:03:47.759 When we add new features, it will assist you in writing the Ruby code.
00:03:53.160 Refactoring will also become somewhat easier.
00:03:59.700 I want to start with a demonstration of the Loopy project with type definitions.
00:04:06.600 I will demonstrate how the hover, go-to-definition, and completion features work.
00:04:11.400 This is older.rb, which represents a POS system.
00:04:17.100 We have the order RBS file that includes method definitions for each payment method.
00:04:24.180 When we hover over each payment method definition, it will display the method signatures.
00:04:30.060 If we point to the block, it will show the type of local variable within the block.
00:04:39.660 The completion feature also works with different payment methods.
00:04:45.180 This includes various Active Record methods.
00:04:50.280 We can use it with methods like 'where' or 'select' to define methods on that type.
00:04:57.660 Jumping to the definition is somewhat supported.
00:05:04.080 If we hover over a constant, it shows the class definition of that constant, allowing us to jump to its definition.
00:05:10.560 By clicking on the total method and selecting go to definition, it jumps to the method's definition.
00:05:17.880 If we have a string here, it will report an error.
00:05:25.500 This is because the gross type is an integer and there is no plus method between an integer and a string.
00:05:30.480 This demonstration illustrates how we can use the IDE effectively with RBS definitions.
00:05:39.840 The next demonstration will cover how to get started with Steep.
00:05:44.520 We will discuss setting up gems, generating RBS files, and running type checkers.
00:05:50.040 This is a demonstration of the demo application.
00:05:58.800 Assuming we have a point-of-sale feature, we have four modules.
00:06:05.640 The order module corresponds to the sheet.
00:06:11.400 The order item module represents the items you get from the shop.
00:06:19.200 The other two modules are cash payments and account payments.
00:06:26.160 These objects represent the payments you give to the shop.
00:06:30.960 Now, let's edit the gem file.
00:06:35.200 We will add some setups, including gems like Steep, RBS, and RBS Rails.
00:06:45.120 We need to ensure we use the previous version here.
00:06:50.880 After adding these, everything will now be installed.
00:06:55.800 Let's see if it works.
00:06:58.800 I want to have a Steep version 1.1.1, which is the latest previous version.
00:07:05.280 Next, we run the Steep command to execute this setup.
00:07:11.040 We need to run RBS correction to create the RBS type definitions.
00:07:18.840 We need to declare the dependencies for RBS libraries.
00:07:25.920 Some gems have RBS files of their classes, but we need that.
00:07:31.560 Here’s a list of required RBS files.
00:07:38.880 In this case, we don't need to include RBS files for gems like Steep itself.
00:07:42.600 We will add the ignore option.
00:07:51.600 Run the RBS correction installation, which will download the necessary RBS files from the gem correction repository.
00:08:00.000 Trigger me. It might take some time.
00:08:06.000 So let's skip this and move on.
00:08:10.680 We need to set up the local gem repository for using RBS corrections.
00:08:18.120 Please read the documentation for each repository.
00:08:24.600 Next, we can install the RBS value break tasks.
00:08:30.540 This will generate RBS files for the database modules.
00:08:35.880 We will have an asking directory here with RBS files for each module.
00:08:41.880 These modules are generated from the runtime information of Active Record.
00:08:49.680 Active Record modules have methods for each attribute.
00:08:56.160 The method definitions are generated accordingly.
00:09:01.680 Now, let's try running the Steep check.
00:09:07.560 Uh oh, I don’t have Steep installed.
00:09:13.560 Let's add it and check it again.
00:09:19.440 Oh, and we forgot to add the Steep configuration file.
00:09:26.160 This configuration file defines our type check targets.
00:09:35.400 Now, I will run the Steep check.
00:09:42.000 Let’s see what happens.
00:09:49.440 The RBS collection is installed. Why do we need that?
00:09:54.600 Okay, unfortunately, it's not working at all.
00:10:00.840 Oh no.
00:10:02.760 I need to skip the demonstration of this part.
00:10:14.760 I'm really sorry we have to skip part of the demo.
00:10:18.000 I had planned to show how we can start type checking existing Ruby code.
00:10:25.440 We generated some prototypes of RBS files and edited them.
00:10:31.560 We will find some problems and fix them.
00:10:38.040 Once we keep the demonstration and return to the presentation,
00:10:45.240 I want to emphasize that we should focus on the parts we work on.
00:10:52.200 There's no need to fix all type errors when starting type checking.
00:10:59.400 You will work on parts of the codebase to add new features or fix issues.
00:11:05.880 Then, you will write the RBS type definitions at that time.
00:11:12.240 This example demonstrates checking the Steep repository itself.
00:11:19.800 There are more than 1700 programs annotated in Steep.
00:11:26.280 In fact, we have more than 2000 entries in this repository.
00:11:36.660 But I find it more effective to write new code with the type checkers' assistance.
00:11:43.560 Another point is that to maintain new safety, you should test every time before using a value.
00:11:50.760 Optional types are useful here.
00:11:57.840 We might need to write some type interpretations to help the type checker understand your code.
00:12:04.920 We may also need inline annotations to support metaprogramming or casting.
00:12:11.880 However, keeping type checking as the last resort is essential.
00:12:18.600 Now, let's discuss how we can write new code using types.
00:12:25.560 I found that type checking against existing Ruby code is not very rewarding.
00:12:33.900 You know that there are no critical bugs because you have tested them.
00:12:39.840 Your team might review them or they are already in production.
00:12:46.440 Steep will only detect minor problems, which may not make much sense.
00:12:54.840 It may require too much workaround or inline annotations for minimal benefits.
00:13:02.400 Therefore, I think writing new code is more beneficial.
00:13:09.840 There are bound to be some unknown bugs in new code.
00:13:15.840 I want to add some features to the demo app.
00:13:21.120 I will show how to define APIs in RBS.
00:13:30.960 I also want to demonstrate that writing the implementation with a type checker will help.
00:13:37.920 Now, we'll allow audience interaction by allowing input of item names.
00:13:44.280 We also plan to add filtering options for updating or predicting payment methods.
00:13:53.880 I want to call this class the 'SearchService'.
00:14:02.760 This is a simple implementation, where the name of the item is passed to the initializer.
00:14:09.840 We construct some queries based on the orders and return the respective orders.
00:14:17.520 The implementation will replace the junction definition.
00:14:25.920 I want the implementation to look straightforward.
00:14:34.560 I will support some other types of queries.
00:14:39.000 Let's add a module called Query with classes representing different payment types.
00:14:48.720 These classes will represent paid by card and paid by cash.
00:14:56.280 I will define methods that translate the input into arrays of queries.
00:15:03.960 This way, we can classify items based on payment types.
00:15:11.160 Next, we will set up the RBS type definitions.
00:15:20.280 Now, let's implement the features.
00:15:28.560 The reader queries will call the respective methods.
00:15:35.880 Next, we will initialize the inject method.
00:15:41.160 We will use a case statement to handle different query types.
00:15:45.960 Queries could be based on name, payment method, or other types.
00:15:54.240 Now let's implement these methods accordingly.
00:16:01.440 The name query will have an attribute.
00:16:09.120 Be sure to account for payment types correctly.
00:16:16.080 At this point, you can do that with joins.
00:16:21.480 This is how to implement the search method.
00:16:29.520 This shows how I’m using type checkers.
00:16:38.640 You'll notice I used union types instead of inheritance.
00:16:46.560 I think it might be beneficial to add some base class.
00:16:54.840 However, with union types, we can do almost the same things.
00:17:02.760 We have the union type of credit name and can use case when for analysis.
00:17:10.320 There are two styles: one uses inheritance and method calls, while the other uses union types.
00:17:18.240 You can choose the style you prefer.
00:17:25.920 Some rules apply, especially when you know the set of types.
00:17:32.640 In this case, you can use union types and case statements.
00:17:38.760 If you don’t have a specific set, inheritance may be better.
00:17:44.280 This relates to the expression problem.
00:17:51.720 Union types generalize optional types.
00:17:58.560 Optional type is equivalent to union types.
00:18:05.280 The type checker will help to analyze all possible cases.
00:18:12.720 If you add new types, the type checker will notify you.
00:18:21.000 This is a small diagram representing how to work with types.
00:18:27.960 We have a concept application called test RBS and design document.
00:18:35.880 RBS type definitions lie between testing and design documents.
00:18:43.920 Writing type definitions is easier to change than implementing or testing.
00:18:50.760 Writing types and code is a back-and-forth process.
00:18:58.440 You write some types, then code, fix issues, and refine types.
00:19:05.520 This highlights the iterative nature of development.
00:19:12.480 Steve can be used, but there are alternatives like RubyMine.
00:19:19.440 RubyMine has its own type checkers yet can support RBS.
00:19:26.760 I had planned to demonstrate this, but it didn’t go well.
00:19:33.600 You will benefit from types when writing new code.
00:19:40.680 Type-checking existing code is less rewarding.
00:19:48.840 Writing types and implementing code should go hand in hand.
00:19:55.200 Using case when without type checking can establish best practices.
00:20:02.520 Having type checkers or writing types can redefine the best APIs.
00:20:11.880 This ultimately leads to better programming.
00:20:17.880 That’s everything I have for today. Thank you for listening!