Crystal: How Using a Compiled Language Made Me Write Better Ruby

Summarized using AI

Crystal: How Using a Compiled Language Made Me Write Better Ruby

Luis Lavena • July 20, 2018 • Paris, France • Talk

In this talk, delivered by Luis Lavena at the Paris.rb Conf 2018, the focus is on the Crystal programming language and its advantages over Ruby, particularly in the context of compiled versus interpreted languages. Lavena begins by explaining the fundamental differences in how Ruby and Crystal operate, highlighting how Ruby is interpreted through its virtual machine (YARV) while Crystal compiles to native code, optimizing execution speed.

Key points discussed include:

- Compilation Process: Crystal's compilation process includes tokenization and parsing, ultimately leading to the creation of native executables. In contrast, Ruby translates code into an instruction sequence for its virtual machine.
- Type Inference: Crystal employs type inference, deducing data types at compile time rather than run time. This feature reduces errors and enhances the reliability of code execution compared to Ruby's run-time checks.
- Macros vs. Metaprogramming: While Crystal lacks some dynamic typing features of Ruby, it offers powerful macros that automatically expand code, enabling compile-time analysis. This enhancement aids in generating efficient and error-free code.
- Error Detection: The compilation phase in Crystal allows for early detection of errors, such as trying to add incompatible types or invoking methods on undefined variables, which provides a significant advantage in development.
- Optimization Techniques: Lavena also discusses various optimization techniques beneficial for both Ruby and Crystal, such as reducing load times using Bootsnap and compiling gems to improve installation efficiency.
- Code Consistency: He emphasizes the importance of maintaining a consistent code style across projects using tools like RuboCop, enhancing team collaboration and reducing disputes in code formatting.

Lavena concludes by encouraging Ruby developers to adopt strategies from compiled languages, such as Crystal, to improve performance, code cleanliness, and efficiency in their Ruby projects. He stresses the potential of applying the discussed optimization practices to streamline development workflows and enhance the overall Ruby ecosystem, making it more inclusive for newcomers.

Crystal: How Using a Compiled Language Made Me Write Better Ruby
Luis Lavena • July 20, 2018 • Paris, France • Talk

Recorded in June 2018 during https://2018.rubyparis.org in Paris. More talks at https://goo.gl/8egyWi

Paris.rb Conf 2018

00:00:11.460 Let me introduce you to Luis Lavena. Hello everybody. The tough part about being after lunch is that everyone will want to take a nap. So, let's talk a bit about Ruby and Crystal, the Crystal programming language.
00:00:23.369 Don't worry; it was just me misplacing my screen. So, my name is Luis Lavena. You can find me on Twitter; feel free to say whatever you want. On GitHub, you can find my username as well, where I upload all sorts of things.
00:00:40.520 To give you a bit of background, I am the creator of Ruby Installer for Windows. So, if anyone has suffered at some point working with Ruby on Windows, that's my fault. Now that I'm somewhat retired after ten years, I think I did some good; the community has grown so much, and nowadays, Ruby runs really well.
00:01:05.910 I'm also a Ruby co-committer. While I'm not as prolific as Nobu or others, some of the comments associated with Ruby for Windows are connected with the work we did on Ruby Installer. I work for a company called Is 17, a digital product agency that sponsored my trip here.
00:01:19.260 I actually moved to France as an expatriate Argentinian, and I want to thank them for hosting me here. Now, this is a technical talk, so before we jump into deep technical stuff, I want to give a warning: I'm from Argentina, and tomorrow there's a French match. Now that I'm here as an expat, it makes things complicated.
00:01:40.900 So, let's talk more seriously about Crystal and Ruby. What differentiates them? We're going to focus on the aspects of compiled languages and how they generate machine code, and what that means compared to interpreted languages.
00:02:32.380 Ruby works by starting with the 'require' command, reading the source code, and converting that into tokens that represent your code. It then parses this and builds what's called an Abstract Syntax Tree (AST). From there, it creates an instruction sequence, which tells the Ruby virtual machine how the code works.
00:02:56.140 This instruction sequence runs inside the virtual machine, now known as YARV (Yet Another Ruby Virtual Machine), from Ruby version 1.9 onwards.
00:03:10.209 Now, how does that compare to a compiled language? A compiled language also reads the source code, tokenizes it, and parses it through a lexer. However, instead of building an instruction sequence, it generates native code that will run natively on the operating system.
00:03:28.590 This means that it compiles the native code, links all those objects, and generates a final executable. Depending on the compiler's complexity, there might be an additional phase between parsing and generating code that analyzes your code, performs type checking, and executes optimizations.
00:04:12.160 At this point, the most interesting part happens during the analysis phase, which I like to call 'magic.' Let's take a look at a specific example: the Crystal language.
00:04:52.780 Crystal has similar phases to a compiled language but also introduces some unique features. Its syntax is inspired by Ruby, and it can directly bind to C libraries without the need for a custom extension API. This means you can write directly in Crystal without any extra layers.
00:05:30.660 While it lacks some dynamic typing and metaprogramming techniques found in Ruby, it offers macros. Macros are automatic code expansions that can be even more powerful than metaprogramming techniques because they allow you to analyze what you generate at compile time, knowing in advance if it will work or not.
00:06:10.620 The syntax is similar to Ruby, and it generates a single-file native executable on your machine. Crystal's compiler is built on LLVM, which offers powerful optimization capabilities.
00:06:25.930 Now, let's focus on one specific aspect: type inference. Type inference in programming means that you have various data types—like integers, floats, and strings—that represent data or objects in your program. Inferred typing means these types do not need to be declared explicitly.
00:06:53.130 If you're familiar with C language, when you do not define a type, the C compiler will complain. However, in an inferred language like Crystal, it deduces the type based on context as it parses the code.
00:07:37.750 For example, if you have an array of integers, Crystal will analyze the values and deduce the type. You can define variable 'A' as an array of 32-bit integers and another variable 'B' as an array containing a number, a string, and a character. If you try to add an incompatible type, Crystal will complain during compilation, ensuring types are checked before run-time.
00:08:30.620 This checking happens at compile time, leading to better code, as you're alerted to potential problems before you run your tests. The same applies to method calls. For instance, if you define a method that adds two values together, and you accidentally attempt to add a string and a character, Crystal will throw an error at compile time.
00:09:16.310 Additionally, if you try to call a method on an undefined variable or nil value, Crystal will let you know during compilation, which is a significant improvement over Ruby’s run-time errors. You can implement techniques in Crystal that can help you handle nil values effectively.
00:09:54.190 As an example, if you work with a worker object, you might have code that checks if the worker variable is defined before performing operations on it. This method of manipulating potential nil values will help when transitioning back to Ruby, making it easier to recognize possible errors.
00:10:34.050 Another challenge we encounter involves accessing variables across threads. Ruby is a single-threaded language, but it can still face issues with global variables being modified by another thread. To mitigate this, you might capture variable values into local variables to ensure consistency and reliability.
00:11:09.250 By doing so, you guarantee that the value accessed is the expected value at that moment, thus avoiding potential runtime errors.
00:11:39.090 We discussed techniques you can borrow from compiled languages. Now let’s talk about optimization techniques. One of these is related to load time. When working with Rails applications, we know that load times can be significant.
00:12:07.180 When you're deploying your application, you need to manage load time carefully to ensure better performance. This involves using techniques like rolling deployments to maintain uptime while you're deploying updates.
00:12:58.919 The time it takes to read files, tokenize them, build instruction sequences, and execute them impacts your overall application performance. For every require call in your Ruby project, this process repeats, further adding to your application’s loading time.
00:13:36.760 This is where the introduction of techniques like Bootsnap comes in. Bootsnap is a gem designed to optimize load times by tracking files that need to be loaded and keeping a cache of compiled bytecode.
00:14:07.240 This allows your application to avoid re-compiling files that have already been processed, leading to a significant reduction in boot time.
00:14:51.039 In fact, integrating Bootsnap can provide up to a 30% performance improvement in your application’s boot time, depending on application size and complexity.
00:15:28.770 Switching gears, let's discuss installation times and dependencies. As many find, updating dependencies like Nokogiri can be time-consuming due to the need to compile native extensions.
00:16:11.639 Using a tool like 'Gem Compiler' allows you to precompile gems for your platform, drastically reducing installation time and managing bandwidth. This means you can optimize both the time and resources needed for dependency installation.
00:16:49.600 In short, by using precompiled binaries, you gain efficiency. Given the time spent compiling extensions each time you deploy, having a precompiled version will save resources and speed up your deployment process.
00:17:29.830 Now let's wrap up with optimization techniques involving code style. Consistency in code formatting is essential. This applies particularly to newcomers who may wish to contribute to projects.
00:18:08.110 By enforcing a standardized style guide, you streamline communication and minimize disputes about coding styles, allowing teams to concentrate on functionality rather than style.
00:18:52.420 There are tools available to aid in code formatting, similar to Go and Rust, which help maintain a consistent codebase while allowing developers to focus on feature development.
00:19:28.930 For Ruby, 'RuboCop' is a formatter that applies the Ruby style guide recommendations and can be integrated into your CI environment. This means that every time someone creates a pull request, you can ensure their code adheres to your style guidelines.
00:20:06.370 With these techniques combined, we can create better, cleaner Ruby code by adopting philosophies and strategies from compiled languages.
00:20:47.760 As we apply these strategies, we improve our workflow and ensure our open-source projects create a welcoming environment for newcomers. So, let’s embrace these improvements for a more efficient and streamlined Ruby development experience.
00:21:25.080 Links to improve your load time, use Bootsnap from Shopify, incorporate pre-compiler gems, and apply code formatting techniques with RuboCop are available for reference.
00:22:08.740 Thank you very much for your attention.
00:30:10.290 Thank you, Luis. Okay, one question. You can find me outside if you need to discuss anything.
00:30:15.700 Thank you for the talk! I have a question about optimization. I've learned a lot from interpreting languages and compiling them. What if I want to optimize my deployment by compiling my gems before installing them?
00:30:35.860 That's a very good question. The drawback is that if you compile on your local machine, it may not work on your server due to differences in the operating systems. Instead, you could pre-compile for the appropriate platform.
00:32:19.150 You can also enhance your development workflow by pre-compiling gems that everyone on your team uses, streamlining the process while saving time.
00:32:39.290 Thank you again.
Explore all talks recorded at Paris.rb Conf 2018
+10