Talks

Ruby Mixology 101: adding shots of PHP, Elixir, and more

RubyKaigi 2024

00:00:08.599 Hello, good afternoon. Let's begin this almost final session for today. I think everyone started after yesterday's karaoke.
00:00:15.720 I will try to make it as fun as possible and not boring. We are talking about a cult called Mixology, but we're not going to drink today; instead, we are going to mix some concepts into Ruby.
00:00:29.359 Okay, let's go. I would like to start with a question: Why is Ruby so impressive? Why does it impress people when they see it for the first time, not just engineers?
00:00:39.399 The answer to this question lies in the very DNA of the language, and we can find it in the definition on the official website. One aspect is the elegant syntax that Ruby provides for readability and writability, which makes the language impressive.
00:01:01.480 However, Ruby's syntax and Ruby itself didn't come out of nowhere. It is a result of thoughtful work and inspiration. Yukihiro Matsumoto invented Ruby because, as I believe, he was not satisfied with the existing tools and languages.
00:01:20.799 It was also about the joy of problem-solving—designing a language that would be pleasant to use has always been, and still is, quite a challenge, and we are all trying to solve it.
00:01:39.520 Ruby has traces to other languages, mostly conceptually, semantically, and syntactically, from Smalltalk and Perl, but Ruby brought a lot of new features to the world of programming languages and continues to evolve on its own.
00:02:04.720 The syntax is a part of this evolution. If we take a look at the last ten years of Ruby since I started actively using the language, we have had many changes, both small and significant.
00:02:30.840 I think 2019 was the most fruitful year for Ruby's syntax introduction. Not all of them made it to the final release, unfortunately, but there were good attempts, and we will probably return to those issues later.
00:03:07.560 We continued adding new syntactical features, small and big, and even in the next upcoming release, we will already have one minor but really cool addition: an implicit argument for procs.
00:03:25.280 I would like to talk about what’s coming after that, because the evolution is not stopping here. We are going to have something new in the next 10, 20 years, whatever we live to see, and Ruby will thrive.
00:03:36.439 They are not going to stop, because language evolution is inevitable, and syntax evolution is a part of it. Why is that? The answer is quite simple: if a language is not evolving, it joins the list of dead languages, and we don’t want that fate for Ruby.
00:03:51.560 So there will be something new, but the pace of this evolution may vary. It could look like we introduce some new features every year, perhaps every Christmas.
00:04:11.240 It’s interesting how and why sometimes we have a lot of new features, and sometimes just a few. For many years, we may use the same syntax as before. Let’s take a look at some historical data and see how some of the features made it to release and when they were proposed.
00:04:38.280 For example, there are features that had been aging for a few years; not all that long. Some features waited for more than five years to finally reach Ruby, while the last ones were proposed and merged within the same year.
00:04:59.560 It’s interesting how the core team and everyone involved in this process reason about adding new syntax when it is a good time to do that, and why it doesn’t happen as soon as someone proposes a feature.
00:05:20.240 Introducing new syntax is not an easy task because syntax changes are irreversible. Once new syntax is added, it cannot be removed from the language. It’s really difficult.
00:05:41.320 We have flip-flop operators too; we tried to remove them, but they are still there, and I think they are going to be around for a while. To introduce new syntax, we need to clearly understand that it will bring more benefits than conceptual complexities.
00:06:01.080 Every time we introduce something new, it adds to the conceptual complexity of the language. So the question is: how do we figure out if a particular syntax proposal is going to be useful or not without even being able to use it?
00:06:26.800 When the proposal is still in draft form, it may just exist as text in an issue tracker or perhaps a branch somewhere in a fork, making it hard for regular users to use this new feature and provide feedback.
00:06:43.760 Thus, the first thing we need to do when evaluating new syntax proposals is to bring more people to experiment with them, to find corner cases, and to share their use cases and examples.
00:07:00.280 This would likely convince the core team and Matsumoto to finally merge the feature. We will see some examples of that, but how do we bring proposals to the community?
00:07:21.960 I have the answer: it's called Ruby Next. Ruby Next is a transpiler and tool for Ruby, and it's an award-winning software here in Japan. It helps to use modern Ruby features and upcoming features even on older versions or alternative implementations.
00:07:38.160 It supports Ruby as far back as 2.2, at least officially. More importantly, it gives you machinery to write your custom transformers or rewrites.
00:07:49.720 You can experiment with new syntax that is not yet available in CRuby or not yet supported by the parser—whatever parser we might use today. Since this year, Ruby Next also comes with a companion project called Rubex Playground.
00:08:13.480 It’s an online code editor specifically designed to work with transpiling Ruby code. It allows you to write Ruby code, define a rewriter for that, and then see how it behaves.
00:08:33.480 You can write simple code using regex; it’s sufficient for experimentation, and of course, you can run this code, transpile it, and see how it looks in your version of Ruby.
00:08:48.279 Everything works, and you probably already guess how it operates: it is built with Ruby wasm, so this talk is yet another talk about Ruby wasm at this conference.
00:09:03.040 I think it's worth naming this edition of RubyKaigi as 'Ruby wasm Ki'. We will use Ruby Next Playground for interactive demos during this talk.
00:09:26.160 Now, let me introduce myself. My name is Vladimir Dementyev.
00:09:35.839 If you find my badge or meet many people here, you can use my GitHub handle, which is Palcon. I maintain many projects in Ruby that you probably have heard about. I work at a company called Evil Martians.
00:09:56.600 Our Osaka team is here, and I’m happy to share whatever you want to learn about Evil Martians—why we are considered 'evil' and what we are doing here in Japan.
00:10:08.240 I have a book here, and what a coincidence! It's a book on Rails. It's currently not available in Japanese, but I hope it will be soon. If you can ask me anything about it, I think I may have a spare copy to share.
00:10:24.720 That's all about the advertisement block. Let's move on to the actual talk. As I'm trying to drink some water, the bottles on the slide and on the one I shared on Twitter depict many different programming languages.
00:10:44.120 I suggest you take a look and try to figure out if you recognize all of them; you might discover something new—that's pretty funny.
00:11:02.960 So, as we've discussed, one way to improve the efficiency of accepting or rejecting syntax proposals is to get more feedback by making these proposals accessible to users.
00:11:20.000 Another option is to learn from other languages. Nowadays, it's hardly possible to find a Ruby developer who isn't also using a second or third language.
00:11:37.200 We all write software that relies on many stacks and platforms, and we would sometimes love to see features from other languages implemented in Ruby.
00:11:53.160 For instance, I already mentioned a shorthand for hashes and keyword arguments, which dates back a long time. Initially, this was quickly rejected by Mats because Ruby did not need it.
00:12:09.120 However, years later, the position changed to him being more open-minded about it, and as a result, it was added largely due to community insistence for this feature, influenced by common use cases in JavaScript.
00:12:25.600 I’m glad this feature was made available—I have been using it for years because it has been supported in Ruby Next.
00:12:43.040 Okay, enough history. Let's look at the future and start mixing some things from other languages into Ruby.
00:13:03.920 The first language I want to talk about is PHP. This is actually how I came up with the idea for this talk. I was programming in PHP before I found Ruby, about 12 years ago—it was a decent language.
00:13:20.200 I hadn’t followed its evolution for years, but then I discovered a video showing how PHP has evolved. PHP is not dead yet; it is also evolving.
00:13:45.200 One thing that caught my attention is the operator they call the 'null coalescing assignment'. The idea is that if the left-hand side is null, we assign it to the right variable.
00:14:04.560 This is the PHP version of this feature, and other languages have similar functionality, like the Elvis operator in Kotlin, which is a bit different but serves a similar purpose.
00:14:33.920 JavaScript also has a version of null coalescing assignment. It's not surprising that we already have a proposal for this in the Ruby tracker, which is quite old and still open.
00:14:50.000 It seems it should be rejected based on the discussions, as there hasn't been enough rationale to add this feature to Ruby. However, I've been thinking: maybe we can take the idea of this operator and make it smarter.
00:15:16.760 We could justify its addition if we can think of ways it could be more useful in Ruby. Before doing so, I'd like to share a new syntax checklist with you.
00:15:41.240 First, it should not conflict with the current Ruby implementation and should raise a syntax error. This is how we gradually avoid complicating our language.
00:16:04.880 Secondly, we must justify the addition of the feature and the complexity it brings. Complexity could come not only from the semantics but also from the implementation.
00:16:29.600 The maintenance of features throughout the life of Ruby is what's truly important. It should be easy for the core team to understand and support in case the initial proposer decides not to support it.
00:16:52.000 After considering how we can introduce null coalescing assignment in Ruby, I came up with this new version of the syntax: let’s call it 'questionable assignment'.
00:17:11.160 Other languages might use two question marks, but I decided to go with just one because having too many questions in the syntax can cause confusion.
00:17:26.160 In this case, the syntax is not supported—of course, it raises a syntax error. This is a static version to show, for example, how one can assign false.
00:17:39.760 If something is assigned, it cannot be changed anymore in this way. This static example does not fully demonstrate the feature, so let's move to the Rubex Playground.
00:17:56.480 This is a working version of this operator built with Ruby Next. We can observe that we can assign false, and if we try to assign one to it, nothing will happen.
00:18:14.120 If we use our regular conditional assignment, it works as expected. This is the primary use case for operation with potentially falsy values.
00:18:31.120 However, frankly speaking, I don’t think that’s enough reason to implement this feature in Ruby. It presents a limited use case, so I started looking for more opportunities.
00:18:54.760 While reading a discussion on the Ruby tracker, I found that what the original request was for—the ability to set a hash key if it isn't defined—led to a similar proposal called 'hash fetch set.'
00:19:17.760 This concept hasn't yet made it into Ruby. So I thought, why not allow this operator to become a bit smarter and context-sensitive?
00:19:34.760 If we know that there is a hash key assignment, we can check if it's set and then update it. We could allow nil values inside, and it would work.
00:19:52.239 Another interesting use case is using it with instance variables, which can lead to code patterns relying on checking if an instance variable is defined or not.
00:20:12.480 I use this pattern frequently and would love to employ a shorter syntax for it. Let’s view this concept in action; it works.
00:20:33.040 When assigning to current user potentially being nil, by using this operator instead of the regular assignment, the method is only called once if it’s nil.
00:20:52.360 However, there's a problem: the Ruby performance team has introduced a new concept called Object Shapes. They encourage avoiding patterns in code for performance optimization.
00:21:11.280 I don’t like the idea of rewriting well-established Ruby patterns for the sake of performance tweaks that may or may not yield tangible benefits.
00:21:28.680 Nonetheless, I don't think that will be the primary reason this feature wouldn’t be accepted. Let’s take a brief look at how the transpiled version of this works.
00:21:45.880 As you can see, for hashes, we check for keys and for instance variables—we are doing the defined check manually.
00:22:01.480 We have some esoteric use cases; perhaps the latest one isn't entirely viable, but I'm waiting for more smarter object shapes to write Ruby code without worrying about the underlying implementation.
00:22:22.760 In terms of our checklist, we pass the syntax error check; we have some use cases, but not all of them are applicable today.
00:22:42.360 Speaking of implementation, effectively, it's a matter of a minor extension to the lexer and parser mechanics but requires careful consideration.
00:23:00.960 That was just a warming-up example. I aimed to demonstrate how we can think and experiment with new features and evaluate their potential.
00:23:12.680 Now, let’s talk about a more interesting language: Erlang, which is my second favorite language. I'm thrilled to show you some Erlang syntax during this talk.
00:23:32.640 If you've never seen it, this is how Erlang looks. This is a popular learning book about Erlang, which is one of the best programming books I’ve ever read, even better than my own.
00:23:50.140 This is a canonical example of pattern matching in Erlang. We have similar pattern matching features in Ruby, and I would say it looks better in Erlang.
00:24:08.560 The expressiveness of Erlang enhances readability because we can use ranges instead of guards, making it more concise and easier to understand.
00:24:32.480 However, this isn’t the only way to write functionality in Erlang. There's an alternative version that utilizes a feature known as method overloading or multi-dispatch.
00:24:52.160 I have been writing Erlang a lot, and I really appreciate this feature. I would love to see something like this in Ruby as a natural evolution of the existing pattern matching feature we’ve had.
00:25:15.760 Indeed, there is already a proposal for this feature. I believe it has the most detailed specifications, but it introduces a new keyword like 'defp,' which resembles something from other languages.
00:25:30.440 I don't like the idea of adding such non-readable keywords in Ruby, as we write our syntax in English.
00:25:47.000 There’s also a recent proposal that's somewhat related—extending endless methods to support arguments forwarding with pattern matching. It has garnered interesting discussions.
00:26:06.520 Let's return to our Ruby code and see how this could be translated into a method overloading version without needing to focus on implementation issues.
00:26:24.160 We could use existing constructs from Ruby, and the pattern on the right of it is all that’s required to implement this functionality.
00:26:42.240 While this feature isn’t supported in the latest Ruby versions, we can create a framework for its possibility.
00:26:58.800 Why do I prefer this syntax? Because it doesn't introduce unnecessary keywords, and the design of this feature resembles patterns found in other languages.
00:27:13.440 The previously mentioned proposal suggests having one 'def' statement and multiple 'end' statements, but I find it detracts from readability.
00:27:29.920 By having multiple 'def' method repetitions, we can maintain clarity in our method boundaries and intentions.
00:27:47.960 Let’s see how this would function. We have the same method from the Erlang book, and we can call it with different arguments and get different results.
00:28:05.840 The last clause catches any unmatched cases, resulting in a method error when incorrectly called. This behavior aligns perfectly with what we want to achieve.
00:28:23.360 We can also use keyword arguments quite naturally, and now let’s see if we use the common pattern from functional languages.
00:28:41.920 We can enhance this implementation to avoid redundancy by creating a definition for arguments, promoting reusable patterns.
00:28:58.960 With the current implementation, we can support positional arguments and keyword arguments, but we do not yet have combined support for both.
00:29:15.440 The question remains: do we need to make the syntax more complex to allow mixed arguments?
00:29:32.160 If you’re using method overloading, you should probably stick to one or the other—either positional or keyword arguments.
00:29:49.520 Another challenge is the lack of guards, but I believe Ruby is already expressive enough to bypass them, thus not adding extra complexity.
00:30:08.960 The advantages of pattern matching methods overloading blend seamlessly with the Ruby object model features such as inheritance.
00:30:24.800 For instance, we could inherit a class with overloaded methods while allowing for flexible use through patterns.
00:30:44.320 However, if we plan to introduce this to Ruby, we need to think thoroughly about method name clashes and how to traverse method lookup mechanisms.
00:31:03.680 For example, we could establish two versions of the same functionality through case statements and overloaded methods.
00:31:29.680 It turned out that under the hood, both versions could compile to the same byte code, retaining the efficiency of Ruby's mechanisms.
00:31:47.360 Now with Ruby 3.3, we can facilitate this restructuring for specific implementations. With all the considerations, it looks promising.
00:32:05.440 Before I wrap up, let’s mix in several additional concepts in Ruby during the remaining time. This may feel intense, but let's finish strong.
00:32:17.920 First up, there has been a push to allow the setting of instance variables directly in method definitions to reduce boilerplate code.
00:32:34.720 Many libraries attempt to simplify this initialization process without needing extensive initializers listing instance variables.
00:32:51.680 The initial proposal on this feature was made 11 years ago. Popular languages like CoffeeScript and Crystal have adopted similar strategies.
00:33:06.720 One typical example that could be modified with this feature would simplify setting up instantiation through keyword arguments.
00:33:23.920 For instance, we could utilize keyword arguments directly for initializing instance variables, enhancing code clarity.
00:33:38.240 Further limiting such functionality to the initializer methods could help tighten control and prevent redundant complexity in other methods.
00:33:53.680 Now, on to an interesting proposal emerged about anonymous structs—it has been pending for 4 years without success due to notation challenges.
00:34:11.040 Research shows that Zig has made strides in this area, emphasizing a focus on immutable structures versus Ruby’s current open struct implementations.
00:34:32.080 In conclusion, I added Elixir to my title, but we've not discussed it yet. The most wanted feature the Ruby community has sought is the pipe operator.
00:34:50.080 This discussion arose just days ago, leading to multiple implementations being suggested. The discussion is evolving, so I urge you to work on it.
00:35:11.480 There was a past proposal for a pipe operator, but it did not gain traction. The new ideas around this feature will potentially push its acceptance.
00:35:31.920 This could provide a means of translating Ruby code into a cleaner representation, giving us valuable functionality with just the right implementation.
00:35:45.920 To summarize, I urge everyone to be proactive in syntax proposal discussions, especially if you can provide examples from your libraries or applications.
00:36:04.800 If you can highlight confusion or edge cases not yet covered by the core team, this will greatly benefit the future of Ruby’s syntax.
00:36:26.840 I have compiled everything I talked about today into a single Rubex example, which can be viewed in an interactive version. There's a short link available for you to open it.
00:36:44.920 It contains many rewriters, and although it's not optimized for performance, it works well. That’s all for today. Thank you very much.
00:37:02.800 I appreciate the support from my colleagues for providing Japanese subtitles, which I hope have helped everyone understand my message today.
00:37:18.280 Thank you!