Talks

Functional Programming and Ruby

While Ruby is object oriented and imperative, it does have some features that allow for functional programming. In this talk we'll compare Haskell, a functional programming language, with Ruby while exploring these common functional patterns: higher order functions, lazy evaluation, and memoization.
Along the way we'll explore how Ruby works internally, find out whether it's a true functional language, and zoom in to take a close look at Ruby 2.0's implementation of the new "Enumerator::Lazy" feature.

Help us caption & translate this video!

http://amara.org/v/FG93/

GoRuCo 2013

00:00:15.650 How about one more round of applause for the great conference? It's been a fantastic event, and I'm really enjoying it.
00:00:20.910 Let's give it up for these guys: Brian France and Cory Stephen, for making this conference so great.
00:00:27.090 I know this is the Gotham Ruby Conference, but what you see on the screen is not Ruby. It's actually a functional programming language called Haskell.
00:00:32.430 Are there any Haskell programmers in the room? I see a couple of hands waving in the back. Can anyone tell me what this code does? Does anyone recognize this algorithm?
00:00:39.510 Perfect, good job! This is the quicksort algorithm. Don't worry, I’m not going to explain this algorithm in detail.
00:00:45.780 So why am I showing this to you? I think it's really important for all of us to look at other programming languages from time to time. We should learn from other languages and not just use Ruby every day.
00:00:58.230 If we always do the same things that we did before, we're not going to learn much. There are a couple of reasons to study other programming languages. The first one is really obvious: when you step outside of your comfort zone and tackle a different learning curve, you learn a lot of new stuff quickly.
00:01:10.140 The second reason is less obvious. To illustrate this point, let me draw an analogy between programming languages and human languages.
00:01:15.630 Just like learning a foreign language, such as Chinese, Greek, or in my case, Spanish, has many benefits, learning other programming languages can provide similar insights.
00:01:26.820 I was lucky enough to marry a woman from Spain, and later in life, I learned Spanish as an adult—it was one of the most rewarding and exciting things I've ever done.
00:01:32.340 One summer, we took our kids to Spain to spend time with her family. While we were there for a few weeks, or even almost two months one year, I immersed myself in Spanish, as none of her family spoke English. I got really used to the staccato way they spoke over there.
00:01:48.270 After a while, I heard English again—maybe I was walking down the street and passed by a tourist, or maybe I called home. It was the first time I had heard English in six or seven weeks, and it sounded a bit different to me.
00:02:00.640 This experience gave me some perspective on my own language and how it sounds to a non-native speaker.
00:02:10.140 In the context of programming, this Ruby conference represents our native language. If you study another language, when you return to Ruby, it might seem different to you. You may begin to use Ruby in a different way.
00:02:40.120 Where does Ruby come from? This is Matz, the founding father of Ruby. Did he just wake up one day and decide to create a new programming language? I'm not sure. Matz isn't here today so we can't ask him.
00:02:56.200 I did a bit of research and found an interesting quote from a Ruby talk mailing list back in 2006. Many of you may have seen this already.
00:03:02.080 This quote provides significant insight into where Matz got the ideas for Ruby. He looked at several programming languages and did what I mentioned earlier—he examined them for inspiration.
00:03:10.350 He specifically mentioned taking a simple list language, adding methods from Smalltalk, and including functionality found in Perl.
00:03:18.730 Matz explored several programming languages available at the time, pulled in different features he wanted to incorporate, and created the new language that we recognize as Ruby today.
00:03:27.460 If Matz is the founding father of Ruby, then perhaps the creators of Smalltalk, Lisp, and Perl are like the grandfathers of Ruby.
00:03:38.600 This is Larry Wall, who created Perl in the late 1980s. While it might seem like a long time ago, it wasn't that long.
00:03:44.000 I don't know much about Perl—I’ve only used it here and there—but I know that a lot of Ruby's regular expression syntax comes from Perl.
00:03:51.430 Another potential grandfather is Alan Kay, who created Smalltalk back in the late 1960s. This was over 40 years ago! If you haven't checked out Smalltalk before, I encourage you to do so.
00:03:57.580 It's an amazing system that includes its own graphical user interface and an integrated visual debugger, cutting-edge for its time.
00:04:04.270 One of the remarkable things Alan and his colleagues did was to take computers designed specifically for Smalltalk and bring them into middle schools to teach programming concepts to kids, perhaps for the first time.
00:04:11.290 The entire language was also designed with education in mind. Alan was a real visionary; he conceived of the idea of a laptop in the 1970s, 15 to 20 years before laptops were built.
00:04:18.100 He referred to it as a Dynabook. I encountered an interesting diagram about Smalltalk in an academic paper around six months ago.
00:04:25.080 This diagram stood out to me; it essentially depicts how Ruby functions. It illustrates the internal class hierarchy of Ruby, including the class class, object class, and integer class.
00:04:31.960 However, if you look closely at the caption, you'll see it refers to Smalltalk's 76 metaphysics. This diagram is nearly 40 years old and represents a language that is not Ruby.
00:04:38.820 It's remarkable how much of Ruby derives from Smalltalk. At its core, Ruby is fundamentally an object-oriented programming language.
00:04:46.520 As Rubyists, we are all object-oriented programmers, but there's one more language Matz mentioned in that quote that we need to explore.
00:04:57.940 Wait, I've seen this picture before—this isn't the first slide! John McCarthy is behind everything that’s happening here, isn’t he?
00:05:07.360 Yes, this is John McCarthy, who invented Lisp back in the late 1950s. If you don't know about Lisp and want to learn more, check out this classic book.
00:05:13.090 Lisp is famously known as possibly the first and most influential functional programming language.
00:05:19.780 It was invented in the late 1950s, and the paper I mentioned earlier discusses symbolic expressions, defining the whole concept of functional programming.
00:05:27.660 This brings me to my main topic for today: functional programming.
00:05:31.920 I want to discuss what functional programming is, whether we can achieve that with Ruby, and what concepts Matz might have borrowed from Lisp.
00:05:42.290 I won't delve into Lisp in detail, but I will introduce you to Haskell. This is the original code we had earlier—quicksort.
00:05:49.610 Haskell is a concise and beautiful language, and I've been studying it over the past few months.
00:05:57.030 Haskell is actually named after Haskell Brooks Curry, a 20th-century mathematician who studied lambda calculus, signified by the Greek letter lambda that represents a function.
00:06:03.530 So what is Haskell? It's defined as a polymorphically statically typed lazy purely functional language, whatever that means.
00:06:10.520 In contrast, Ruby is an object-oriented dynamic language. Ruby and Haskell are two very different languages with little in common, but today I aim to compare Ruby and Haskell and illustrate their differences.
00:06:30.340 To start, I want to take a couple of minutes to define functional programming. Many of you may be familiar with it, but some might not have a clear understanding.
00:06:42.680 Let's clarify this to get on the same page, and then I'll discuss three techniques that functional programmers frequently use.
00:06:51.920 I'll first explain these concepts, demonstrate them in Haskell, and subsequently examine how we can apply the same principles in Ruby.
00:07:03.330 These techniques include higher-order functions, lazy evaluation, and memoization.
00:07:09.170 Let's start by discussing functional programming, which is simply programming with functions.
00:07:13.760 Functions have a domain and a range; you input a number X, which goes into the function and is processed to yield an output of f(X).
00:07:18.430 The principal concept behind functional programming is that a pure function, when given the same input (in this case, X), will always produce the same output (f(X)). Functions are independent of time; it does not matter when I call this function or how often I call it.
00:07:42.860 The benefit of adhering to pure functions lies in their predictability, which helps you write better software. If I construct an application with distinct functions that always return the expected output, the software will become more robust and reliable.
00:08:05.510 However, writing pure functions can be challenging since there can be no changing states or memory used within the function.
00:08:10.960 The function must remain isolated without side effects and cannot interact with the rest of the application. Essentially, the functions operate as black boxes.
00:08:31.420 Despite their complexity, the potential benefits of functional programming are substantial. If you want to delve deeper into functional programming's philosophical underpinnings, I recommend checking out Rich Hickey's presentations.
00:08:44.370 Rich Hickey is a brilliant individual who created the language Closure, a dialect of Lisp that operates on the Java Virtual Machine.
00:08:53.470 Hickey is also a captivating public speaker, and his videos are worth watching as he discusses profound concepts about state, time, and data in a compelling manner.
00:09:05.060 Now, I want to shift gears away from philosophical discussions and direct our focus towards practical coding examples, starting with higher-order functions.
00:09:12.510 When we speak of higher-order functions, we're referring to functions that either take other functions as parameters or return another function as their result.
00:09:31.139 Let's look at a specific example in Haskell where we create a list with a series of numbers.
00:09:37.520 In Haskell, you can define a list simply as [1, 2, 3]. You can also create a list from a range, similar to this: [1..10].
00:09:47.800 In Ruby, we achieve a similar outcome by utilizing arrays or ranges. For example, I can use (1..10).to_a.
00:09:55.040 While at first glance Haskell and Ruby may appear somewhat alike, let's dive deeper and see how to calculate the first ten squares.
00:10:02.560 In Ruby, we can achieve this using the syntax: (1..10).map { |x| x * x }.
00:10:09.900 In Haskell, the equivalent code does employ a slightly different syntax; we express it as [x * x | x <- [1..10]].
00:10:15.220 Although the language structures are different, you can observe that they function relatively similarly despite originating from distinct paradigms.
00:10:23.170 Now, let's define what higher-order functions are. We just explored an example of a higher-order function when we used the 'map' operation.
00:10:30.570 A higher-order function is one that accepts a function as an input and can return a different function as output.
00:10:36.820 In Ruby, I can show you this in a more verbose way by explicitly passing a lambda as an argument. This highlights that we're working with functions as inputs.
00:10:43.420 For example, in this Ruby line, I'm taking (1..10).map(&:lambda) and using lambdas to define the operation I'm applying.
00:10:52.120 In Haskell, you could achieve this behavior by defining a similar mapping function that takes another function as an argument.
00:11:03.420 Both languages have the functionality to work with higher-order functions, highlighting that, while the syntax differs, the underlying principles share striking similarities.
00:11:11.370 Next, let's explore the philosophical approach that functional programmers adopt in contrast to object-oriented programmers.
00:11:21.530 As object-oriented programmers, we often conceptualize problems in terms of interacting objects and their respective states.
00:11:28.060 However, functional programmers focus more on their data—how it is processed and transformed through a series of functions.
00:11:36.390 For instance, if I want to compute x squared plus one, I might apply a function to obtain the intermediate and final result step-by-step.
00:11:46.820 In Ruby, you'd likely implement the same logic through chaining methods together logically. As we analyze how each language processes the same input, we'll notice subtle differences.
00:11:57.720 Let’s consider how each language processes a series of numerical transformations by applying multiple functions in succession.
00:12:06.850 In Haskell, you may have an elegant way of combining these transformations using function composition, yielding a straightforward result.
00:12:15.560 By applying higher-order functions, such as a series of 'map' calls, you'll see that Haskell performs operations in a functional pipeline.
00:12:22.630 In contrast, Ruby allows you to chain the transformations using similar methods. Both languages return the same results, but the underlying mechanics differ subtly.
00:12:29.490 The abstract understanding of functional programming principles can enhance how you think about Ruby's own paradigms.
00:12:36.550 Let's delve deeper into the next core idea of lazy evaluation. Before we proceed, we should understand the critical aspect of what lazy evaluation entails.
00:12:45.840 In Haskell, I can define an infinite list of elements, which allows me to access elements on demand without pre-calculating them upfront.
00:12:55.860 When utilizing lazy evaluation, Haskell waits until values are actually needed before computing them.
00:13:03.750 For instance, I can declare an infinite sequence using the expression [1..] and obtain values on-the-fly when requested.
00:13:12.250 This concept highlights the power of Haskell's lazy evaluation and how delayed computation can enhance performance and memory usage.
00:13:19.390 Now, can we achieve something similar in Ruby? With the release of Ruby 2.0, we introduced a feature called Lazy Enumerator.
00:13:27.230 I can use a similar approach in Ruby, but it requires a bit more typing than in Haskell.
00:13:35.140 Using the expression (1..Float::INFINITY).lazy, I initiate an Enumerator and chain operations efficiently.
00:13:45.900 The balance between ease of use and performance involves a trade-off; Ruby’s syntax is more verbose and, by default, is eager in comparison to Haskell's inherent laziness.
00:13:58.520 The Lazy Enumerator allows us to efficiently process sequences without evaluating all elements at once.
00:14:06.030 When using the 'collect' method on a Lazy Enumerator, the operation will not compute until we explicitly request the computation with methods like 'first' or 'take'.
00:14:21.350 The essence of lazy evaluation lies in controlling computation until the very moment it's needed, enhancing memory efficiency.
00:14:36.710 Next, let’s discuss memoization, a programming technique used for caching results to enhance performance. Think of it as an optimization method.
00:14:55.160 A classic example of where memoization can be beneficial is the Fibonacci sequence, known for its recursive calculation.
00:15:04.450 The nature of computing Fibonacci values traditionally leads to exponential time complexity, especially when invoked recursively.
00:15:12.780 In Haskell, I might implement the Fibonacci sequence in a simple but inefficient manner using recursion. The slow Fibonacci function computes the same values repeatedly, leading to sluggish performance.
00:15:27.160 To remedy this, we can incorporate memoization. By storing the computed values, we avoid calculating them multiple times, leading to optimized runtime efficiencies.
00:15:39.700 When defining a memoized Fibonacci function in Haskell, I can leverage Haskell's capabilities to define recursive functions naturally.
00:15:51.490 I can define a slow 'fib' function with specific cases for 0 and 1 and a catch-all for all other values using recursive pattern matching.
00:16:01.750 The benefit of this approach is that Haskell allows for elegant recursive definitions that leverage previously computed values.
00:16:08.050 To illustrate, I can use memoization and define my Fibonacci sequence efficiently. It's a straightforward method in Haskell, taking advantage of lazy evaluation and recursion.
00:16:21.210 The way functional programmers often conceptualize this is by constructing infinite lists of values, where all Fibonacci numbers are generated by mapping the recursive definition.
00:16:37.060 This paradigm shift allows for cleaner, more efficient code structures that serve as caches for computed values, streamlining the process.
00:16:48.840 Now, let's explore how we might tackle this in Ruby. Although Ruby does not lend itself to lazy evaluation and memoization as straightforwardly as Haskell, we can adapt similar patterns.
00:17:02.420 In Ruby, implementing caching can be achieved through a hash data structure, allowing values to be stored once computed.
00:17:12.890 Utilizing the ||= operator, I can easily build a memoized Fibonacci function without the same elegance as Haskell but still with effective results.
00:17:24.560 In conclusion, while Ruby isn’t a purely functional language like Haskell, it still possesses functional programming features that can enhance the way we approach coding.
00:17:39.160 Today, I encourage all of you to take time away from Ruby and delve into other programming languages. Even exploring human languages can foster new insights.
00:17:53.080 Your experience with other languages offers you a fresh perspective when you return to Ruby, which might encourage you to use Ruby differently.
00:18:01.590 Remember, Ruby offers many functional programming features. Matz indeed drew inspiration from Lisp, especially in elements like blocks.
00:18:09.070 Although Ruby is not a pure functional language like Haskell, it provides room for both object-oriented and functional programming paradigms.
00:18:20.750 That's it from me today. If you're interested in learning more about Ruby internals, check out my book entitled "Ruby Under a Microscope," which I am currently revising for print.
00:18:33.580 Now, we have a few minutes left for questions. If anyone has any inquiries, feel free to ask, and I will do my best to answer them.