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.