00:00:12.590
Hello everyone, I'm Joe, and I am here to talk to you about functional Ruby.
00:00:20.310
Let's get started with it. Oh, I have this remote for the slides that is disconnected.
00:00:27.500
That's the end of that. My most important job is being a daddy to that cute little girl; her name's Lucy.
00:00:33.450
I promise I won't show any more pictures of her, but hey, she's adorable!
00:00:39.210
I am on Twitter, GitHub, and LinkedIn @j_leo_3. Say hello on any of those platforms; I'm a friendly person.
00:00:47.430
My second most important job is running a company called Deaf Method. It's an agile software consultancy based in New York City.
00:00:55.170
We do have openings for remote engineers, PMs, and designers. If you are great at what you do, then we're hiring!
00:01:03.080
I co-wrote the third edition of the book called The Well-Grounded Rubyist.
00:01:11.520
At this point, you might be saying, "Actually, Joe, this guy David A. Black wrote The Well-Grounded Rubyist, and you did not write it."
00:01:17.100
You are both correct and incorrect. David wrote the first two editions of The Well-Grounded Rubyist and made the text super popular.
00:01:22.259
Then they came to him and said, "Hey David, could you write another edition for a more recent version of Ruby?"
00:01:29.280
He said, "No thanks, I'm finished. But I have this friend Joe who would love to do it," and I did.
00:01:37.020
One of the things I focused on was the trend toward functional programming in Ruby.
00:01:42.960
This was borne out in today's talk by Matz, where he discussed functional programming language features from languages like Elixir and F#.
00:01:50.250
So, I wanted to extend the book by writing a chapter called "Ruby and Functional Programming."
00:01:55.920
David thought that was a good idea and said, "Yeah, go ahead and do it." I published the book, and here we are as Rubyists and friends.
00:02:02.009
If I had a nerd card, the top of it would say, "loves to read fiction." I decided to intersperse some fictional books in here.
00:02:08.970
This is for no reason other than I love it, and they might be tangentially related to the slide.
00:02:14.360
But also, if you're bored and you’re sitting over in one of those seats and you can't get out without annoying people, at least if you're going to Google something, I'll give you something good to look up.
00:02:20.569
Okay, so let's talk about functional programming and purity. In the functional programming world, there is a lot of discussion about pure versus impure functional programming languages.
00:02:27.450
I think that this is a limiting kind of discussion. So let me break that down.
00:02:35.159
Ruby is a functional programming language—this can generally be agreed upon—because it supports first-order functions.
00:02:41.280
By that definition, a language like Lisp or any other functional programming language like F#, Elixir, Scala, or Clojure are all functional programming languages.
00:02:49.290
So what about Java? Ever since Java 7, Java has also supported first-class functions with closures.
00:02:55.170
Therefore, it's also a functional programming language. Here's where you'll really hate PHP.
00:03:03.080
Since its inception, PHP—the language that every Rubyist loves to hate—has supported first-order functions.
00:03:10.620
Unfortunately, there are some people out there who do some really terrible things with PHP, giving it a bad name.
00:03:17.370
However, it is still a functional programming language. So what's the problem?
00:03:22.410
The issue is that you can't look at this list and say, "Well, there’s a list of functional programming languages" because they are not viewed primarily as functional programming languages.
00:03:29.790
What makes a purely functional programming language? A purely functional language guarantees referential transparency.
00:03:37.950
That just means if you call a function with the same arguments, you will get the same result no matter how many times you call that function.
00:03:44.020
For example, one plus one will always equal two, and one plus two will always equal three.
00:03:50.250
No matter how many times you call a valid Ruby function, you will get the same result. That’s referential transparency.
00:03:57.390
Ruby does not guarantee this; we already know this because everything with a bang operator will change the state and give you a side effect.
00:04:04.410
This means they will not be referentially transparent. But importantly, Ruby does support referential transparency to some degree.
00:04:10.620
A purely functional programming language also guarantees immutability, and Ruby does not fully support this.
00:04:17.370
You might say, "Well, hang on a second, doesn’t Ruby support immutability?" It does have frozen objects.
00:04:23.520
So, you can freeze an object, and you can’t change it after that. So isn’t that immutable?
00:04:30.150
Also, Ruby 3 may introduce frozen strings, but for now, let's see.
00:04:37.080
In terms of immutability, actually no. If you dig into the source code of Ruby, you’ll see that freezing a string is merely flipping a bit.
00:04:43.020
So, if you can just flip that bit at runtime—using a certain library—you really don't have immutability.
00:04:49.170
So, Ruby is not a purely functional language. However, that is a strict definition.
00:04:57.390
By that definition, your favorite functional programming languages are also not purely functional languages; Lisp is not, Scala is not, Closure is not, Elixir is not.
00:05:04.320
So what does that leave? Haskell. Are there Haskell programmers here today? Anyone who enjoys playing with Haskell? There they are!
00:05:10.620
Those are the folks who are going to come up to me after the talk and tell me about all the things I got wrong.
00:05:16.440
And that’s great; they write in a purely functional programming language. But it really doesn’t apply to the mass of programming languages out there.
00:05:25.410
I would argue it really doesn't have to—most of the time, side effects are good.
00:05:32.240
Even immutability can be good in certain circumstances. Side effects, of course, are necessary to do interesting things.
00:05:39.120
Like inserting things into a database or printing things to a screen. While there are purely functional ways to skirt this and avoid side effects,
00:05:45.960
you don't have to adhere to this to adopt a functional programming philosophy and approach in Ruby.
00:05:52.830
So what do most people mean when they talk about functional programming?
00:05:59.700
They often refer to removing side effects. Though it can get a little trickier, some may talk about lazy evaluation.
00:06:06.480
They might mention generics or tail call optimization. I think if you’re not in this room, you’re likely in the other room, where they’re discussing pattern matching.
00:06:12.930
These are all big concepts in functional programming. To be honest, when I started out as a developer, those concepts were intimidating to me.
00:06:20.130
To my discredit, I didn’t challenge myself to learn them. Luckily, I eventually got it.
00:06:27.490
Even more fortunately, I found a friendly environment to start learning: the Ruby programming language.
00:06:34.610
Ruby really does support functional programming and has been doing this for a while.
00:06:41.040
It is going to support more functional programming paradigms in the future.
00:06:48.060
My goal today is to present what is often thought of as complex information in a simple, easy-to-understand format.
00:06:54.680
I'm even going to do some live coding, make a bunch of mistakes, and you can all yell at me.
00:07:01.270
We’ll dive into some tough concepts and hope you leave here understanding that you can take these concepts and build upon them in your own work.
00:07:07.460
Okay, let’s start with the easiest of topics: side effects. We know that if I create a string called Joe and then uppercase it, my string is unaltered.
00:07:13.200
There’s no side effect there. But the moment I append an exclamation mark, I have altered the string forever.
00:07:19.260
This creates a side effect. If it's done outside the scope of a function, I've altered the state of my program.
00:07:25.950
Now, I don't have a functional program anymore. However, the exclamation mark is there.
00:07:32.730
Side-effect-free Ruby should be easy—except it’s not. Even things that we commonly use can have side effects.
00:07:38.430
Take the double Chevron operator; it’s a side effect that irrevocably alters my array. So, we must be careful with our actions.
00:07:46.080
Another example is exceptions. If I try to fetch a key that does not exist in a hash, I'll receive a key error.
00:07:53.370
This isn’t a big deal, but when an exception is raised, it's considered a side effect. If it's not handled, I can't guarantee referential transparency.
00:08:00.000
This means I can’t ensure consistent results. You have to understand the language features and context to maintain a side-effect-free landscape for your code.
00:08:09.210
Let me give you a relatively pedantic example. Here on your left is some suboptimal code.
00:08:18.360
Anyone want to tell me why? Easy one—just shout it out.
00:08:24.990
Yes! A divide by zero exception is raised.
00:08:31.740
There’s also no exception handling, and I’m mutating state from inside the method.
00:08:40.620
The reason I’m bringing this up is that examples like this are rampant in ActiveRecord or even ActiveRecord helpers.
00:08:47.670
We think statements like, "Calculate a grade and update the student with that grade" seem straightforward, but they are not.
00:08:56.700
When we dig a little deeper, we see that this function modifies state. Thus, it is not side-effect-free and isn't referentially transparent.
00:09:02.580
This brings us to our other example on the right, which performs just one task and is referentially transparent.
00:09:09.210
Ultimately, from both object-oriented and functional programming perspectives, this highlights that design theory can overlap.
00:09:16.650
Let's take it a step deeper. Currying sounds complicated, but it is not.
00:09:24.240
Currying is breaking down one function with multiple arguments into multiple functions with one argument each. Let's use an easy example: addition.
00:09:31.470
We can define a function called 'add' that takes parameters A and B, returning the sum.
00:09:39.150
When we call it with two integers, we get what we expect.
00:09:45.180
To create the curried version, we need to create two functions, each with one argument.
00:09:52.140
So, I'll define a function that expects one argument, which produces another function that expects the second argument.
00:09:58.790
When I call it, I can pass in two arguments. It's straightforward.
00:10:05.190
Partial function application goes hand-in-hand with currying.
00:10:10.340
This happens when we pass any amount of arguments less than the total number of expected arguments.
00:10:17.640
In this case, if I pass in only one, I receive my curried function back.
00:10:24.240
Later, when I call it again with any argument, the expression evaluates to an integer.
00:10:31.950
That’s all just to illustrate the concept. Ruby simplifies this process with the `curry` keyword.
00:10:39.660
Calling `curry` manages both currying and partial function application.
00:10:46.530
You can use it to create your functions, and Ruby will take care of it under the hood.
00:10:54.360
Next, let's tackle a more advanced example—finding multiples for a discussion on generics.
00:11:00.990
We’ll create a lambda to search for multiples of X within an array.
00:11:07.350
The lambda will yield each element to see if it matches our multiple condition.
00:11:14.070
When I call this function, it should yield the correct multiples.
00:11:20.520
For example, finding multiples of 3 from an array like [30, 40, 50] should give us 30.
00:11:27.060
Now, let's make it more generic. Generics help create disparate focused functions from the generic definitions.
00:11:35.370
A simple example of this would be a curried version of the find multiples function.
00:11:41.520
I'll create a generic function that find multiples of...
00:11:48.540
This can easily be adjusted to find multiples of any number just by partially applying the function with different inputs.
00:11:55.110
Now, let’s advance to streams. The reason for creating higher-order functions is to process large data sets efficiently.
00:12:02.050
In functional programming, the goal is to create functions that operate on potentially infinite sets of data.
00:12:09.850
For example, we can leverage lazy evaluation to only process data as needed.
00:12:17.350
Let’s demonstrate finding the first multiples again, but this time without specifying a data set upfront.
00:12:24.450
I can create an infinite range, using Ruby’s ability to handle infinite sets.
00:12:30.790
Then, I can use that range to evaluate against multiples through a lazy evaluation strategy.
00:12:37.660
Once again, I want to yield results only when required.
00:12:45.440
Now, I want to create a function for first multiples with a specified number.
00:12:51.500
So let’s say, find the first ten multiples of a number.
00:12:59.480
This way, it will give me results like the first three multiples of 12 or 10.
00:13:07.500
I should see results without needing a traditional data structure each time.
00:13:15.660
Okay, we have a little time left—about 13 minutes—let’s briefly discuss recursion.
00:13:23.350
I am not going to attempt to teach recursion—it’s a vast topic and better addressed by experts.
00:13:30.850
But I want to mention the terminal clause in recursion. That’s the condition under which recursion stops.
00:13:37.740
This clause is critical for tail-call optimization, and for that, Ruby supports it—but you have to set it up.
00:13:45.660
Let’s take a look at some recursive functions, such as factorial.
00:13:53.150
First, I will write a standard recursive factorial and then make it tail-call optimized.
00:14:02.710
For the standard factorial, we return X if X is less than or equal to 2.
00:14:08.880
If not, I will call it again with the argument adjusted.
00:14:15.370
A recursive function should work for smaller numbers, but will hit limits on larger inputs.
00:14:22.660
To make this tail-call optimized, I must ensure it only calls itself last.
00:14:29.430
For that, I introduce an accumulator to gather results during recursion.
00:14:36.930
The accumulator allows the function to call itself without duplicate operations.
00:14:43.960
With this setup, even large numbers will compute correctly without stack overflow.
00:14:51.310
Essentially, the definition of an accumulator is to carry results through your recursion.
00:14:57.880
Now, if you'd like to learn more, there's a great resource available: Manning has a 40% off promo.
00:15:06.250
I also had a wonderful experience working with the Ruby Tapas team on partial function application.
00:15:12.370
In that episode, I delve deeper into the currying concept, providing additional examples.
00:15:19.140
Thank you all for coming and for listening!