Ruby
The Functional Rubyist
Summarized using AI

The Functional Rubyist

by Joe Leo

In his talk titled "The Functional Rubyist" at the Ancient City Ruby 2019 conference, Joe Leo explores the integration of functional programming concepts within the Ruby programming language. He aims to demystify functional programming for attendees and demonstrate how these concepts can be applied in Ruby, a language traditionally known for its object-oriented principles.

Key Points Discussed:

  • Introduction to Functional Programming in Ruby: Joe Leo posits that Ruby, while primarily an object-oriented language, possesses functional programming capabilities through features like blocks, procs, and lambdas. He likens Ruby's flexibility to that of other languages like Java and PHP, which also embrace functional programming paradigms.

  • Understanding Purity in Functional Programming: He discusses the notion of purity in functional programming where referential transparency and immutability are emphasized. Leo notes that while Ruby does not guarantee these ideals, it can still employ functional programming techniques effectively.

  • Key Functional Programming Concepts:

    • Side Effect-Free Functions: Demonstrated through examples of string manipulation and array operations, Leo illustrates the importance of maintaining side effect-free functions to adhere to functional programming principles.
    • Currying and Partial Function Application: Leo defines currying as transforming a function with multiple arguments into a sequence of functions each taking a single argument. He provides examples of how this can simplify coding and enhance function reusability.
    • Generics: He relates generics in functional programming to finding suitable abstractions in object-oriented design, helping to shape better function structure in Ruby.
    • Streams and Lazy Evaluation: Leo emphasizes the importance of processing potentially infinite data sets through techniques like lazy evaluation, which optimizes performance.
    • Recursion and Tail Call Optimization: He explains how proper recursive functions can manage performance issues, advocating for tail-recursive functions to prevent stack overflow.

Significant Examples and Illustrations:

  • The use of simple Ruby examples to showcase how functions can be created to avoid side effects, including calculations of grades and finding multiples.
  • Illustrating currying through practical examples where functions can be partially applied for efficiencies.

Conclusions and Takeaways:

  • Leo concludes by encouraging programmers to embrace functional programming concepts within Ruby to enhance coding practices. He emphasizes that understanding these concepts shouldn't be intimidating and can be approached through Ruby's flexible nature.
  • As a closing note, Joe Leo invites attendees to engage in the Ruby and functional programming community, ensuring that learning and sharing knowledge continues beyond the conference.
00:00:12.900 Thank you all for coming! I'm so excited to be talking at the Ancient City Ruby. I am very thankful to all of you who have stayed to the end of the conference. We're on this journey together, and I am very happy!
00:00:20.440 Yes, give yourselves a round of applause! I am actually really grateful. I asked to go on a little bit early because my talk is really long. So for the next two and a half hours—just kidding—actually, I'm really happy to be giving this talk at the end of the conference.
00:00:34.120 It’s called The Functional Rubyist, and it’s kind of a test of my theory. My theory is that I can teach functional programming concepts to people and really lower the bar to get everybody involved. What better way to test that theory than to do it at the end of two grueling days of conference, where I’m the only thing separating you from the beach or from the airplane that you're about to get on and go home?
00:00:49.180 So let’s get into it! This is the Functional Rubyist primer. That’s a cute little girl—her name's Lucy—and I put her up here because she’s my daughter and she’s super cute. I originally had a picture of myself up here with all my details—my Twitter, GitHub, LinkedIn—and I looked at the slides and thought, "That’s really stupid; they’re going to see me, I’m right here!" So this is me. That’s my daughter. I’m Joe Leo, and I’m really friendly on social media.
00:01:13.870 You can find me on Twitter or GitHub; I’m Jay Leo 3. As Andrew mentioned, I run a company called Death Method. It’s a software development shop in the mold of other great software development shops that have come before it. We are based in New York City, and while we're not always hiring, we are a company that is always looking for really smart people. So if you’re interested in remote work or if you happen to be a stone's throw from New York City, feel free to reach out!
00:01:52.509 I co-wrote the third edition of a book called The Well-Grounded Rubyist. We have a few copies here. Does anybody want a book? Seriously, I’m giving it to you right now, okay? Don’t worry, I won’t throw them! Come on up afterwards, and I’ll sign them too. I feel super awkward about signing books, but I’ll do it! The Well-Grounded Rubyist is one of the greatest Ruby books ever written, and I can say that without sounding arrogant because I didn’t write the first one or the second one. This guy, David A. Black, wrote those.
00:02:27.459 He’s a good friend of mine and an outstanding Rubyist and former colleague. He wrote the first version of this book, which was a tremendous success. Most folks, at least of a certain age, coming into Ruby read this book. I was one of them. I fell in love with Ruby because of it. It was so successful that he did another one, which was also successful. Then the publishers came to him and said, "Hey, will you do another one?" He said, "No, I’m tired of it," but he mentioned that he knew a guy named Joe who might want to take it on. That guy was me.
00:03:08.019 So I updated the book and worked with David. We added some new content to make it comprehensive. I decided to add functional programming because Ruby has been making strides towards embracing it. Not only has Ruby always had functional programming concepts, but the zeitgeist has started to embrace functional programming concepts again, so it was the perfect time to include it. Here we are, Joe and David, Rubyists and friends.
00:03:50.109 When I’m not writing technical books—which is almost all the time—I love to read fiction. This sometimes makes me an object of derision at work, where a lot of my colleagues think that fiction is a waste of time. But I stand by my love for fiction! I’ve sprinkled some of my favorite books into this talk for two reasons: first, it kept my attention while I was making these slides, and second, if you wander off and aren't really into what I'm saying, at least you have something to Google on your phone. These are two books I read in the last year that have nothing to do with each other—they're just good books.
00:04:29.620 Now, let’s talk about purity, because I find that when we bring up functional programming, we often get caught up in semantics. I can say something like, "Ruby is an object-oriented language, but it has some functional programming support." I hear this all the time. I don’t disagree with that statement. However, I will also say that Ruby is a functional programming language with which you could do some object-oriented stuff, if you want. Both statements are true, and they are not in conflict with one another.
00:05:04.870 Ruby is a functional programming language because it supports functional programming paradigms. It does this primarily, though not exclusively, through blocks, procs, and lambdas. Certainly, nobody would argue that Lisp is a functional programming language; it’s been around forever and our predecessors told us that it’s functional, and we believe them. It looks like lambda calculus with all those parentheses.
00:06:03.250 But what if I told you that Java is a functional programming language? Now I'm getting a little controversial, I would assume. Ever since Java 7, Java and the JVM have supported functional programming concepts with closures! There are other JVM languages that are kind of functional-first, such as Clojure, Scala, and Kotlin. So the JVM has lots of room for functional programming.
00:06:40.630 PHP has always supported functional programming concepts, so to slim down the definition, we start talking about purity. We say, "Well, okay, maybe those are functional programming languages, but they're not pure functional programming languages, so they don’t really count." What is a pure functional programming language? Well, it guarantees referential transparency, which is a mouthful, but what it really means is that if you call the same function with the same arguments, you will get a consistent result every single time. Another way of saying this is that it has no side effects.
00:07:36.690 Well, Ruby does not guarantee referential transparency. There are all kinds of examples where we can see this; it’s really easy to write side effects in Ruby code. We do it all the time. Also, a purely functional language guarantees immutability, and Ruby does not. You may say, "Well, hang on a second, you can freeze your objects in Ruby, right?" You can! Starting with Ruby 3, all strings are going to be frozen by default. So isn’t it immutable? Well, it turns out not really.
00:08:03.080 When you dig under the hood a little bit, you can see that in the Ruby bytecode, the C implementation of Ruby uses a little FL freeze flag. Your objects are really frozen with this flag that gets set. If it’s set to 1, then it’s frozen. If it’s set to 0, then it’s not. Interestingly, you can even change this at runtime! So all the things you froze can actually be unfrozen at runtime. If that’s the case, then you can’t say that Ruby assures immutability.
00:08:40.360 If it sounds like I am splitting hairs, it’s because I am! As soon as you start talking about what a pure functional programming language is, Ruby doesn’t fit. Java and PHP, for sure, don’t fit. Lisp doesn’t fit either. The only one that fits this mold is possibly Pascal. So, how many here write Haskell code? There they are! They don’t even want to admit it. Those are the folks who will come up to me after this talk and tell me all the things I did wrong, and I appreciate them.
00:09:24.000 Haskell has a purely functional programming language. What they do has referential transparency, and everything is immutable. You can’t change the state; you can’t change anything. It’s purely functional! But that’s it. It leaves no room for anybody else. I would argue the side effects we get from programming are necessary. You’ve got to do interesting things every once in a while! You’ve got to print things to the screen, do some I/O, save something to a database—essentially, do the stuff that makes our programs work.
00:10:08.560 While it’s possible to execute these functions and eliminate all the side effects, the fact that you are doing them with some side effects doesn’t make you wrong. What matters when you are trying to program in a functional way is your intent. It matters that you try to program with functional programming concepts in mind, and that’s what I’m going to go through. So, what do most people mean when they say FP? They mean a bunch of stuff.
00:10:49.300 They refer to things like generics, currying, lazy evaluation, and, of course, removing side effects. They also mean concepts like tail call optimization. That stuff was intimidating to me when I first started out, even a few years into coding. I had a few languages under my belt. I knew Java and Ruby and a few others that I don’t even like to talk about anymore. I started to learn Scala, and when I got to the chapter on tail call optimization, I was completely intimidated. I’m not proud of that, but it’s true. My eyes glazed over as I tried to read about stack frames and pointers—it just made no sense, so I pushed it away and suffered because of it.
00:11:48.490 I didn’t jump into Scala with the same kind of zeal that I had for Ruby. Even when I learned some other functional programming languages later on, such as F#, Clojure, I could learn and hack, but I was not digging into the depths of what those languages were about. I don't want that experience for you! If you know a little about FP, or nothing at all, or even if you know a lot about it, I want you to understand it at a conceptual level and not be intimidated. The best way to do that is to let Ruby be your guide. My goal for the next thirty minutes or so is to dive into some of these concepts. I’ll do some live coding; I might screw things up; you’ll yell at me; I’ll fix them... or I won’t! But we’ll move on, and we’ll learn a few things together.
00:12:28.919 Let’s start with easy stuff—side-effect-free Ruby. We already know about this. For example, if we create a string named 'Joe', and then we call upcase on it, we have upcased the string without changing it. Easy, right? That’s side-effect free. Let's do another easy example: mapping and filtering. Let’s create an array first and stick some numbers in it. We can say, "I want to map that thing and add 3 to it." We get a mapped array where the original array hasn’t changed.
00:13:31.360 The same is true with filtering; I won’t go through them in detail. However, in Ruby, if we hit that exclamation mark, we introduce a side effect. It’s all about intent! Ruby’s language was built with that intent. It’s hard to find efficient titles with an exclamation mark, but Dr. Seuss came through for it! To illustrate: with a string, if we call upcase with a bang, we change the string forever—this is a side effect.
00:14:11.020 Now, let’s think about our array. What if we get some variable named X and at some point in our program we say, "Well, that thing really belongs in our array," and we use a fancy double-chevron operator? Bam! We’ve created a side effect—our array has changed forever. While this seems like a small thing, it’s easy to overlook because we think we’re doing the right thing by using the cool Ruby syntax.
00:15:04.080 However, it leads to a side effect. When we take a hash and say, "Let’s fetch a key," this can also be a side effect. If you program in a way where there’s no exclamation mark, there’s nothing in the method telling you it’ll raise an exception, but it just does. How many times do we try to find something that doesn’t exist in our program? It happens all the time. If you write a method without capturing that exception, you’ve created a side effect.
00:15:49.000 So let’s consider this quickly. Here we have a calculate_grade method. The method on the left has something wrong with it because it’s doing two things: calculating the grade based on scores and updating the student object with the grade. As soon as we do this, we create a side effect. The one on the right is side-effect free. I understand why the left method is used; it appears to separate concerns—but it’s lying. It says "calculate_grade" and then also updates the grade.
00:16:35.200 In object-oriented design, this violates the single responsibility principle. When you write cleaner code or remove side effects, you obey some functional programming principles, but there’s also an underlying object-oriented principle here. When we level up, let’s talk about currying. To discuss currying, I have to talk about break downs. And if you're going to have a breakdown, I recommend doing it in New York City with Holden Caulfield.
00:17:43.650 Currying is a concept I didn’t understand at first; it’s actually really simple. Currying is simply taking one function with many arguments and turning that into many functions, each with one argument. This concept comes from mathematics, and the term comes from Haskell Brooks Curry, who wrote about it. It can also be done in programming languages, so keep that in mind!
00:18:14.210 Let’s look at a simple example. Here I have a lambda function called add that takes two arguments and adds them together. Blammo! There’s the add function. I can call it with the dot syntax, which is significantly nicer. If I call add with values 3 and 4, it adds them together correctly. Now, to get a curried version of add, I need to create two functions, each with one argument. There it is—curry dad! The difference here is just that we’ve got two functions. The value returned is the same.
00:19:19.740 However, I can’t call it the same way. Why not? That’s right! The curried function only takes one argument. I need to call it with one argument, and then I have to call the resulting function with another argument to get my final result.
00:20:02.640 Now let's discuss partial function application. Currying and partial function application go hand in hand—they are very similar concepts. Partial function application occurs when you pass in fewer arguments than a function's arity, resulting in another function. For example, if the function takes four arguments and you only pass in three or fewer, these arguments can be partially applied to the function, and you’ll get a simplified version of that function back.
00:20:57.950 In this illustration, I’ve shown an add function that takes two arguments, A and B. But what if I knew A? I could apply it and return a function that just takes B. Here’s the kicker—I know it’s four o’clock on a Friday, and you might not want to think so deeply about currying and partial function application. But you don’t have to! Our friend Ignatius J. Riley—the laziest of all heroes in modern American fiction—would suggest you just say curry, and it handles both! In Ruby, you just say curry.
00:21:43.150 Let’s try it with something simple: finding multiples. Take a number X and find it amongst a set—a simple example is to call select on an array. If it yields an element, we check if it’s divisible by X. If it is, we want it; otherwise, we skip.
00:22:39.330 For example, if I wanted to find multiples of three among the numbers five, six, seven, eight, and nine, I should expect to get six and nine. That’s fantastic! If I wanted to find multiples of five, I should get just five. Great! Now, here it is for posterity—a very simple function that accepts two arguments.
00:23:19.430 Let’s try to partially apply the function. Let’s say, "find multiples" equals "find multiples dot curry." It returns another function, as expected, so now I should operate the same way. If I say three, it gives me the correct multiples. I can also pass in just one argument and call it with an array to get the correct output. Wow! I have been working with generics! As soon as you use partial function application, you’re creating generics.
00:24:07.600 Generics are functions that return other functions. By calling "find multiples of three," I get a generic function that operates on arrays seamlessly. Therefore, I argue generics in functional programming are to what objects are in object-oriented programming—the right abstraction for real world representations.
00:25:08.790 With objects, I’m trying to find the right abstraction for the real world and modeling it in code while building on top of those objects. Functional programming works similarly; I’m looking for calculations or functions that meet specific requirements. We can approach this by constructing a generic function from which I can build more specific functions.
00:26:01.200 Let’s take a brief look again: the generic form is "find multiples of" while the individuated functions include "find multiples of three" and "find multiples of five." Now, let’s talk about streams. Streams are an essential concept for functional programmers and ties back to the idea of working with arbitrary sets.
00:27:43.099 In object-oriented programming, you often think in terms of finite sets, like what gets returned by a database or what gets input by a user. But functional programmers aim to determine calculations that can process any size of data set. Streams can be infinite or really large: for example, 'one to infinity' can be treated as a stream. Every word in a dictionary could also be considered a stream.
00:28:48.640 Since these data sets could be quite large, we don’t want to iterate across everything. Therefore, we employ lazy evaluation. In Ruby, beginning with version 2.6, we have the ability to create infinite sequences without using a float constant.
00:30:06.164 So let’s write a simple function that returns the first multiples of a number based on some criteria. In this function, we’ll use the lazy evaluation feature and check for multiples from an infinite set. Instead of iterating across all numbers, we’ll select the numbers satisfying our multiple condition. We apply lazy evaluation and capture the required number of terms only.
00:31:21.340 Let’s test the function. If I want to get the first three multiples of three, it gives me the right output. Similarly, I can check for other numbers and obtain the correct results. We can utilize Ruby’s stream and lazy evaluation feature to handle potentially infinite streams without running into performance issues.
00:32:18.820 Finally, we need to discuss recursion. While I won’t be teaching about recursion itself, it involves determining terminal clauses. Recursive functions often include common examples; finding factorial, Fibonacci series, and so forth, each with their own respective terminal clauses.
00:33:48.490 The reason we recurse goes beyond just academic interest; it often leads to optimized performance of programs. Many might have thought until this point that tail call optimization wasn’t possible in Ruby. However, with specific instructions sent to the Ruby compiler, it can optimize tail recursion. Tail recursive functions are defined such that the last operation in a function is just another call of the same function.
00:34:36.230 Let’s explore a factorial example. We’ll establish a factorial function with an X input and return an accumulator in each call until we hit the terminal clause. This approach will yield effective recursive and tail-recursive functions that work seamlessly even for larger numbers while preventing performance costs due to stack overflow.
00:35:30.350 Both recursive and tail-recursive functions can efficiently handle operations, but it’s crucial to think intentionally about your functional programming approaches. Where can you learn more? You can look into the book! Remember that people don’t write technical books to become rich.
00:36:20.500 If you didn’t receive a book at the talk, feel free to tweet at me, and I’ll send you one! Additionally, I created a section for RubyTapas, which was released recently, that dives into partial function application. I explore more about currying there, along with several intriguing applications.
00:37:07.610 Lastly, I want to thank everyone at Ancient City Ruby, including names I could remember—Mary Ann, Vidal, Andrew, Suzanne, Diana, Matt, and others I met even at lunch today! I apologize if I missed anyone. This conference, like writing a technical book, is really crafted out of love for the community. Thank you all for sticking around!
Explore all talks recorded at Ancient City Ruby 2019
+3