Talks

Ruby programming with types in action

RubyKaigi 2022

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!