RubyConf 2021

Joyful Polyglot: Beautiful insights from many languages

Joyful Polyglot: Beautiful insights from many languages

by Nick Barone

In the talk "Joyful Polyglot: Beautiful Insights from Many Languages," presented by Nick Barone at RubyConf 2021, the speaker explores how insights from various programming languages can enrich the understanding of Ruby and improve programming practices. Barone emphasizes that each programming language approaches coding differently, guiding developers in unique ways to structure their thoughts and code.

Key Points Discussed:

- Importance of Language Diversity: Barone shares his experience of learning from multiple languages, suggesting that exposure to different programming paradigms enhances problem-solving skills and coding style.

- Currying in JavaScript: He illustrates currying functions, a concept in JavaScript, by demonstrating how to transform code for image processing into a more legible structure using curried functions, which can be directly translated to Ruby.

- Scala’s Companion Objects: Barone discusses companion objects in Scala and their ability to access private members, showcasing how similar patterns can be applied in Ruby using modules to effectively separate data and state in code.

- Java Interfaces: He examines Java interfaces and how they can be leveraged in Ruby by creating wrappers around dependencies, enabling clearer interaction with external libraries.

- Spring Framework Beans: Barone highlights the Spring Framework's ability to wire components together automatically, suggesting ways to achieve similar dependency injection techniques in Ruby, ultimately promoting separation of logic and the timing of execution.

Significant examples:

- Throughout the talk, Barone provides coding examples, transitioning from JavaScript to Ruby to demonstrate concepts like currying and dependency injection, emphasizing legibility and maintainability of code.

- He shares insights from a real-world chatbot project, illustrating how incorporating these principles led to clearer, more beautiful code.

Conclusions/Takeaways:

- The talk concludes with a reminder that learning from various programming languages enriches Ruby programming and enhances the clarity and maintainability of code. Barone encourages experimentation with functional patterns and novel implementations within Ruby, advocating for continued exploration of code aesthetics and effective structuring.

- He stresses the significance of writing code that is not only functional but also beautiful, as this contributes to deeper satisfaction and understanding in programming work.

00:00:10.080 I'm Nick, and I'm a first-time speaker, so let's all collectively pretend like I know what I'm about to do. I'm going to talk about things I've learned from other programming languages. In my career, I've learned a lot, having worked in a lot of different languages. I think this is all of them, but don't quote me on that.
00:00:26.960 The first thing to note is that each language has a different way of thinking about code. This perspective comes from both the structure of the language itself and the way communities have created the libraries that you use when working in that language. This guides your thinking and helps you explore different ways to write code and organize your thoughts. All that learning is expressible in any other language. That's part of what makes programming complete.
00:00:48.480 So, this talk is really about looking at features from other languages—ways that these languages think—and how we can apply that thinking, either directly or as inspiration, in Ruby. I'm going to cover four specific concepts: currying functions in JavaScript, companion objects from Scala, interfaces from Java, and beans from the Java Spring framework. Unless you think I'm a Java fan, I'm not.
00:01:16.240 First, let's talk about currying functions. Canonically, a curried function is just a function that returns another function. Here's a straightforward JavaScript example where we rewrite the addition operation as create functions. This can be directly translated into Ruby using procedures. However, we also have a neat feature called `.curry`. Essentially, it indicates that I'm a function that takes two parameters: one now and one later.
00:01:41.040 The first time I learned about curried functions was when I needed to deal with callback hell in JavaScript. Here is a chunk of JavaScript that goes through a directory and creates various images while preserving the aspect ratio. We're going to recomposite it into a more elegant current function setup. I first pulled out the part that gets the aspect ratio into its own function, then the part that resizes the image, and now I have a concise block of code that eloquently describes everything I'm doing.
00:02:05.760 For each file, the code gets the aspect ratio for each width and resizes, making it much more readable than before. Now, let's get to the Ruby example you are all here for. This is before currying, where we move a bunch of objects from one location to another in S3. Ironically, when I curry it, it gets a bit bigger. Don't worry if you can’t read everything; I will go through each piece.
00:02:23.680 The first thing that this lets us do is to slice up our code in a novel way. I’m building a couple different iterators that I can pick up and carry around. That’s what I mean by pulling out the middle slice, allowing me to recompose my code in terms of functions. While we're familiar with dot chains or mixins, this approach lets me do it with functions as well, achieving a nice composition.
00:02:39.680 One of the things I really value about maintaining a functional style is the clear description of all the dependencies my code has, both in terms of the client and the information I need. I can just look at the function signature since there's nothing in there that doesn't come from the parameter lists. This clarity makes it easy to do dependency injection, as all my dependencies are parameters. Consequently, it becomes much easier to test each function because we don't need to keep track of state.
00:03:02.240 In practice, when I write tests, I go through my library file, write a function or two, and a test for each function. By the end, I've achieved around 90 to 95 percent test coverage without much effort. So to summarize, we can slice the code differently, compose functions, clarify dependencies, which simplifies dependency injection and makes testing more straightforward.
00:03:37.359 Now, let's move on to companion objects from Scala. This concept involves having a singleton object with the same name as a class, which is a core language feature. Companion objects have special relationships and can access each other’s private members, functions, and variables. In Ruby, we can kind of mimic this using introspection, but it's not ideal.
00:03:58.720 After struggling with this concept, I came up with a way that feels better. I've created a module alongside some boilerplate code to manage the new objects while having the names match. I also implemented a struct and extended it with the module. This allows me to cleanly separate the data used for computations from the state of those computations, which is cleverly managed through the struct with its various tokens.
00:04:28.160 Separating these two allows for easier hand-offs among various components, creating a more organized structure. This separation helps me manage my statics and methods more intuitively—putting all transformations in one place and actions in another. With the separation, I can apply any data structure that represents computation and leverage any functions I want.
00:05:02.560 This separation of actions and transformations enables distributing computing more effectively since we can organize data and states clearly. What this ultimately helps us achieve is an easier way to write tests and manage state. With interfaces and how they show up in other languages, we can describe everything that a class should do without defining the code that accomplishes it.
00:05:32.960 In Ruby, we can simulate interfaces by raising exceptions, but that's not very satisfying. I've started wrapping my dependencies, drawing inspiration from the interface pattern, even if it doesn't fully imitate it. For example, an S3 wrapper provides an iterator for bucket items and a function for moving them.
00:05:54.320 This pattern allows us to write our own domain-specific language, so we can specify the actions that matter to us. For instance, when interacting with Dialogflow, I create the data structure needed to build requests, facilitating easier interaction. This approach simplifies testing, allowing us to mock our dependencies and think in the data structures we actually want to use.
00:06:20.720 To summarize, this type of pattern enhances readibility and organization in our code, thereby making it easier to test our components. We have a lot of room for experimentation and innovation, providing the opportunity to explore new ideas for structuring our code.
00:06:43.680 For an interesting real-world example, I developed a hobby project that connects Airtable as a contact list to transcribes conversations with a bot on Slack. Such projects allow for the exploration of new features and the integration of various concepts from different languages, which has vastly improved the readability and functionality of my code.
00:07:15.920 To wrap up, we’ve discussed curried functions and their ability to slice computations into manageable pieces, as well as companion objects and their value in separating concerns. Interfaces provide clarity, and the separation of data from state enhances modularity and flexibility in our code.
00:08:03.680 Now, I'd like to open the floor for questions. Please feel free to ask anything about the concepts we covered or if you'd like to dive deeper into a specific topic.
00:08:34.720 One of the concerns raised is regarding the idiomatic use of Ruby in the examples presented. While various languages offer insights and practices, some patterns illustrated may diverge from standard Ruby conventions. It's essential to balance ideas from different languages with Ruby's unique idiomatic expressions to maintain clarity and functionality in Ruby code.
00:09:12.160 Another point of contention is related to dependency injection demonstrated in the samples. The lack of clear object states or constructors might indicate a potential misunderstanding of utilizing object-oriented programming principles in the examples shown. Establishing constructors with injected dependencies would align the patterns more coherently with standard OOP practices.
00:09:45.360 In response to these challenges, it's beneficial to explore language features that reflect the patterns discussed. For instance, using dry-rb could provide enhanced structure and clarity to the functional patterns we've explored.
00:10:07.760 Regarding the separation of concerns in script code, I’ve found that organizing utility functions into libraries and keeping higher-level scripts as documentation can significantly improve team collaboration, especially with junior developers. This structure makes it easier for them to engage and contribute without becoming overwhelmed by implementation details.
00:10:39.840 Engagement with these concepts enhances programming practices overall. It's essential to explore and embrace experimentation in coding, allowing for new insights that can transform our approaches to common problems and tasks. By exchanging ideas and discussing various paradigms, we can improve our understanding of different patterns and enrich our own coding experiences.
00:11:15.360 As I conclude, I want to highlight that programming—whether it's Ruby or any other language—thrives through exploration and curiosity. These discussions foster growth and inspire creativity within our coding practices, leading to better coding habits and techniques.
00:11:57.440 Thank you for attending this talk. I hope everyone gained valuable insights, and I encourage you to dive deeper into these concepts in your own work. Your questions and participation are greatly appreciated.