Side Effects

Summarized using AI

The Functional Rubyist

Joe Leo • November 18, 2019 • Nashville, TN

The video titled "The Functional Rubyist" features Joe Leo speaking at RubyConf 2019 about the integration of functional programming (FP) concepts in Ruby. Joe begins by sharing a bit about himself, mentioning his role at Deaf Method and his co-authorship of The Well-Grounded Rubyist, with a focus on Ruby's capabilities for functional programming.

Key points discussed include:

- Understanding Functional Programming: Joe outlines the basic definitions, emphasizing that Ruby supports first-order functions, making it recognizable as a functional programming language despite not being purely functional. He compares Ruby with languages like Lisp, F#, and Elixir, mentioning that many languages claimed to be functional have side effects which disqualify them from being purely functional.
- Concept of Referential Transparency: He explains the importance of referential transparency and immutability in functional programming. Joe highlights that while Ruby can achieve certain aspects of these concepts, it also allows for state changes through operations such as using the bang operator.
- Side Effects and Pure Functions: Joe delves into side effects, cautioning developers to be mindful of functions that alter state, using coding examples to illustrate side effects that arise during programming in Ruby.

- Currying and Partial Function Application: He introduces currying as the breakdown of functions with multiple arguments into a series of functions each taking a single argument. This concept is demonstrated using the curry keyword in Ruby and is tied to enhancing function utility through partial application.
- Generics and Higher-order Functions: The discussion evolves to generics, illustrating how they can be utilized to create flexible and reusable functions. Joe outlines the importance of lazy evaluation in functional programming to process large datasets efficiently by evaluating results only when necessary.
- Recursion and Tail-call Optimization: Finally, the video touches on recursion, explaining the requirement for a terminal clause in recursive functions and how tail-call optimization is achieved in Ruby by utilizing an accumulator to avoid stack overflow on larger inputs.

In conclusion, Joe stresses that Ruby's support for functional paradigms enhances its usability and encourages developers to engage with functional programming practices. He reassures attendees that it is possible to adopt functional programming philosophies without strict adherence to the principles of purely functional languages. The session ends with an invitation for further learning resources and a thank you from Joe Leo to the audience.

The Functional Rubyist
Joe Leo • November 18, 2019 • Nashville, TN

RubyConf 2019 - The Functional Rubyist by Joe Leo

Functional programming’s popularity is on the rise and support for FP is growing in Ruby. But can you really write functional code in Ruby? More importantly, why should you? Learn how Ruby enables functional programming concepts while maintaining highly readable and fun-to-write code.

#rubyconf2019 #confreaks

RubyConf 2019

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!
Explore all talks recorded at RubyConf 2019
+88