Talks

Things you can't do in Ruby

Things you can't do in Ruby

by Piotr Niełacny

In the video 'Things you can't do in Ruby,' speaker Piotr Niełacny discusses his journey in learning Ruby and highlights some limitations of the language. Initially drawn to Ruby while searching for a Python book, he shares a few frustrations he encountered over three years of usage. The video covers specific topics about Ruby's capabilities and limitations, clarifying how to work around them without delving into C extensions.

Key Points Discussed:
- Inheritance Limitations: While Ruby supports inheritance, it has certain syntax rules, such as the need to specify module names when including classes to avoid type errors.
- Object Unfreezing: The Ruby standard library only allows freezing objects but not unfreezing them, which can be cumbersome in practice.
- Changing Object Classes: While you can change the internal representation of an object, Ruby does not allow for easy class changes of objects, leading to different behavior despite the same representation.
- Method Arguments: Using instance variables as method arguments requires extra work, thus writing more code than expected.

Piotr then outlines how to implement solutions for these issues in pure Ruby. For example, he explains how to use Ruby's dynamic linker to create aliases, handle structures, and how to alter object behavior that defies the standard limitations. A significant part of his demonstration shows:
- Multi-Inheritance Workaround: Tricking Ruby into recognizing an object as a new module, creating a form of multiple inheritance.
- Unfreezing Objects: How to effectively alter classes and unfreeze objects by changing their pointers.

Throughout the presentation, practical coding examples illustrate these concepts. Piotr shows live code demonstrations to exemplify the aforementioned points, emphasizing that despite its limitations, Ruby allows for creative and efficient problem-solving. He concludes with a note on implementing changes in class structures without relying on complicated extensions and encourages the use of his gem for including modules into classes.

Overall, the video provides insights into the versatile nature of Ruby, reaffirming the idea that with the right approaches, many restrictions can be bypassed, leading to innovative solutions within the Ruby programming environment.

00:00:18.199 Hi everyone, my name is Piotr Niełacny, and I wish to share my story about why I chose Ruby about three years ago. I went shopping to buy a Python book, but I couldn't find any. As I was leaving, I found a book about Ruby. I sat down to start reading it, and after the first chapter, I thought to myself, 'Oh my God, I can't do anything with this language.' After three years, I have learned a couple of things I can do with Ruby, and I would like to share that short list today.
00:01:12.640 First, let's talk about inheritance. You can do this in Ruby, but there are some syntax implications. You can't include a class into a class directly; you need to specify the module name after including it, or else you will get a type error, which is not fun. Another thing is object unfreeze. In the standard library, you have only the freeze method on each object, but you can't unfreeze an object, which can be quite annoying. Additionally, you cannot easily change the class of an object. Even though you know that the internal representation of the object is the same, the methods are different. Why is that? Lastly, for a bonus point, similar to CoffeeScript, you can't use instance variables as arguments in methods without some additional work. This means writing much more code.
00:02:24.680 Normally, I should end this presentation and say goodbye. However, what if I told you that you can do all these things in Ruby? I want to explain step by step how to create a solution without writing a C extension. You might think I will write a C extension, but that’s not true. First, you need to clone the MRI repository and find the Ruby headers. After this, I want to clarify that I won't be writing a C extension. So, what can we find in this file?
00:03:19.599 For example, we’ll start with constants. The first four lines you will find are the object IDs of the objects. For instance, the object ID of 'nil' is always four. When you execute 'nil.object_id', you will always get four; this is a magic number. We can simply move this to a Ruby module; it’s just a matter of copy-pasting. Next, we can find in this file aliases and also move this from the C file to the Ruby module. But how can we create aliases in pure Ruby? We can use the dynamic linker, which is part of the Ruby standard library. Note that the dynamic linker doesn’t have much documentation.
00:04:40.360 When you want to create an alias, you need to require the dynamic linker and then import the extending module. By executing 'type alias' this way, you can create aliases for types. Another thing you can find in the Ruby header file is structures. You can also use the struct method from the dynamic linker. In Ruby, every object has a basic structure and additional components attached to it. When we want to create more complex objects, we can use this basic structure and add additional elements to it.
00:05:55.000 After importing everything, we will have all constants, aliases, and structures in our Ruby module. To refer to an object in Ruby, we use the object ID introduced in Ruby 1.9. It’s the method 'object_id'; in Ruby 1.8, it was just 'id'. If we multiply the object ID by two, we obtain the integer representation of the object's pointer in memory. When we refer to this object, we can create an object of a class or a structure. For example, an 'air' object is composed of its basic structure and some additional elements.
00:07:01.680 So, how can we accomplish multi-inheritance? We can trick Ruby into thinking that an object is a new module because a module is a singleton, and the class of the object is pointing to the module. We can also unfreeze an object; it’s quite the opposite of freezing. By changing the pointer of a class, Ruby thinks that this is an object of another class. I can demonstrate that today.
00:08:56.000 Here, I have a plain installation of Ruby from RVM. When I require my Ruby file, I can create a class of my array and include it as a module. My array is just an array, but due to the same internal representation, we can change the class of this object. I will demonstrate this. Right now, the class is 'my ARR', and I will change it to the array class. I can define a method, open the class again, and include the array, creating an object. I can then change the class of this object to an array and execute it. I can also freeze the object, but if I try to modify it, it won't work. However, I have an 'unfreeze' method now, which allows me to modify the object again.
00:10:18.720 If you write in CoffeeScript, you may be familiar with lots of syntax, just like we have in Ruby. You will see how I encounter an error that tells me I can't use formal arguments. This error appears because when parsing the recognizer, you cannot pass an instance variable into method declaration in that way. To debug this, I find that Ruby does not recognize this instance variable correctly, so I need to adjust my implementation.
00:11:00.680 When I fix the method name to avoid using an instance variable, I also point out the issue with passing arguments at all. After this change, everything seems to work well. I need to change the name of the required argument because Ruby does not allow certain names. The implementation ensures the empty methods created are filled with instance variables, and each should work without errors.
00:14:30.800 Now, let’s create a class in Ruby using our new syntax. Everything seems to be functioning properly; we can create accessors to this instance variable. It should return the corresponding name. Fortunately, nothing is impossible when it comes to Ruby. If you want to include a module into a class, you can download my gem called 'include.' It works great on Ruby 1.3, but I haven't tested it on 2.0 yet, so you may want to give that a try.
00:16:43.080 It's noteworthy to mention how to unfreeze some objects. While discussing security, it’s possible to create an untainted object. If you find the proper constants, you can do this, but be cautious as it may not be thread-safe. There are some ways to un-extend objects, such as using a Ruby library available on GitHub. This task can be challenging to implement purely in Ruby.
00:19:22.360 Before I conclude, I want to address a question regarding class changes. If you set the class of your instance to something like 'Fixnum', certain unforeseen behaviors might occur. You can extend a class from array and make certain checks to see how things interface with each other. In case of any errors such as a syntax issue, you can troubleshoot until you find a working solution.