00:00:00
Hello, everyone! Welcome to my talk.
00:00:02
Today, I will be discussing functional programming (FP) for fun and profit.
00:00:07
The title might be misleading because, as I rehearsed this morning, I realized it's not as fun as I thought it would be.
00:00:18
But since you’re already here, you might as well stay till the end!
00:00:27
I want to open up with a GitHub article titled "Functional Programming is Finally Going Mainstream." This article discusses the increasing adoption of functional programming over the years.
00:00:48
You might have seen discussions, tutorials, or explanations about functional programming concepts here and there, as well as memes and parodies related to some of its concepts.
00:01:07
You might be wondering what functional programming really is and whether you should care about it if you just want to write your Ruby code.
00:01:18
After all, Ruby is known for being object-oriented, where everything is an object.
00:01:36
Today, I want to convince you that functional programming can help us write better Ruby code.
00:01:41
Before we dive into that, let me share a bit about myself. My name is Jenny—it's easier for everyone to call me that—and two big events happened in my life two months ago: I started working at Shopify and moved from Taiwan to Toronto, Canada.
00:02:05
I primarily program with Ruby, but there was a time when I almost transitioned to Elixir, which is one of the main reasons I am giving this talk.
00:02:18
You’ll soon find out that I drew a lot of inspiration from Elixir for this presentation.
00:02:31
Also, if you visit my personal website, you will see a string of chicks that follow the cursor wherever it goes, which is kind of therapeutic.
00:02:43
Okay, with that introduction out of the way, let's talk about the functional programming paradigms.
00:02:56
According to Wikipedia, functional programming is a way to classify programming languages based on their features.
00:03:06
There are many programming paradigms out there, such as object-oriented programming, characterized by message-passing between objects and encapsulating state and behavior.
00:03:16
Functional programming, which is the focus of today's discussion, has its own unique set of features, which I will talk about shortly.
00:03:27
To help us understand these concepts better, let’s first look at a mental model.
00:03:39
Consider three elements: value, behavior, and time.
00:03:47
To illustrate this, let’s look at an example: a function 'puts' that prints out the string "Hello, World!" and returns nil.
00:03:54
In this case, we see two components: behavior—what the function does, and value—what it holds.
00:04:01
In another example, if we assign 'rubyconf mini' to a variable 'curve' and later change it by uppercasing it, we are illustrating the element of time.
00:04:14
Between lines of code, the bytes in the machine change, resulting in the value of the variable 'curve' being updated.
00:04:27
To understand what time represents, we can think of it as the value of an identity we hold on to during a specific period.
00:04:42
This is my mental model of what object-oriented programming looks like: an object encapsulates value and behavior, while time is implicitly involved because values can change over time.
00:05:00
In functional programming, each element has a distinct concept associated with it, and the goal today is to explore each of these elements.
00:05:17
Let's begin with the first element: value.
00:05:27
When discussing value in functional programming, we need to address immutability.
00:05:40
Immutability means that once you initialize a value, you do not change it.
00:05:58
In Ruby, this is achieved by freezing objects, such as an array.
00:06:06
When defining objects using the class keyword, you should avoid using attribute writers and accessors, which allow other objects to mutate its attributes.
00:06:20
This goes against the definition of immutability, and thus, we must understand why immutability is important.
00:06:36
With immutable objects, we eliminate shared state complexities. We don’t need to care about when and where objects change because they never do.
00:06:48
Consider this example: I have a flight scheduled for November 14th, and if my friend copies my flight and changes the date to November 13th, it reflects on my flight as well.
00:07:03
This occurs due to shared state: by mutating state, we can run into unexpected bugs.
00:07:24
In programming, immutability helps us avoid these pitfalls.
00:07:34
Now, how can we implement immutability in Ruby? The first piece of advice is to think twice about every state limitation you introduce.
00:07:50
Ruby, at a language level, doesn’t support immutability natively like some other programming languages, so it's up to us to avoid writing mutable code.
00:08:02
This good habit will reduce the likelihood of producing bugs.
00:08:15
For instance, there’s a gem called 'dry-struct' that is part of the dry-ruby collection.
00:08:25
This gem offers powerful and useful tools supporting functional programming ideas, including immutable data structures.
00:08:40
In Ruby 3.2, there’s also a new feature called data defined, which provides simple immutable value objects. You can play with this in the latest version and find excellent documentation.
00:09:04
So, although we have tools to aid us, it’s equally important to be mindful of state mutations and their consequences.
00:09:20
We have now covered the value aspect of functional programming. By enforcing immutability as a constraint, we create programs that are easier to debug and reason about.
00:09:38
Next, let's examine behavior in functional programming.
00:09:54
The core concept is the use of pure functions.
00:10:05
A pure function meets two conditions: it depends only on its input arguments and does not depend on any external values.
00:10:15
For example, if we create a function that uses the Time class, it’s not pure because it depends on the module, resulting in different outputs even with the same input.
00:10:36
In contrast, if we pass the timestamp itself as an input, we create a pure function, as its output can now be predicted based on the input.
00:10:50
The second condition for a pure function is not mutating the state, which implies having no side effects.
00:11:02
If a function alters the string value passed to it, it cannot be considered pure, as doing so mutates its state.
00:11:20
To make this function pure, we must create a new string without altering the original.
00:11:35
Why do we want to restrict ourselves in this way? Joe Armstrong, the creator of Erlang, points out that all languages come with an implicit environment.
00:11:50
This implicitness can lead to unexpected consequences and confusion.
00:12:09
Avoiding implicit environments allows us to focus on smaller, manageable parts of a problem.
00:12:16
This approach leads to improved efficiency and bug prevention, as we can concentrate on details.
00:12:29
To summarize, by striving for pure functions, we minimize the complexities that arise in programming.
00:12:45
The next element we’ll discuss is time, particularly in relation to processes and concurrency.
00:13:00
In object-oriented programming, concurrency can be challenging due to time being an implicit factor.
00:13:12
This leads to potential issues, such as race conditions and deadlocks when multiple processes run simultaneously.
00:13:28
The infamous Global Interpreter Lock (GIL) in Ruby restricts it such that only one thread may execute at any time, improving safety but reducing concurrent capabilities.
00:13:47
On the other hand, functional programming tackles this issue by explicitly treating time as a separate element, which can simplify concurrency.
00:14:05
In an example from Elixir, a functional programming language, concurrency is inherently implemented through its actor model.
00:14:25
In this model, each process acts as an individual unit that can only communicate with others through message passing, ensuring no shared state.
00:14:39
Elixir processes are lightweight, allowing millions to run simultaneously, thereby enhancing concurrency.
00:14:54
Ruby has introduced a similar concept called 'Ractor' starting from version 3.0, which aims to improve concurrency.
00:15:07
In a Ractor model, every Ractor can run in parallel without sharing state, making computation safer and more efficient.
00:15:20
If we attempt to send objects between Ractors, they must be immutable to maintain the simplicity of state.
00:15:32
I’m looking forward to future developments in Ractor and the potential it offers for Ruby programming.
00:15:45
In conclusion, functional programming promotes the use of immutable data and pure functions.
00:16:02
Together, these concepts not only enhance code reliability and maintainability but also provide an excellent foundation for concurrent programming.
00:16:17
The best part is, you can apply these principles in Ruby!
00:16:29
I hope you found value in this talk and encourage you to explore further into functional programming.
00:16:45
Remember, adopting a new paradigm requires practice and adaptation.
00:17:02
These are the resources that I found useful, and I will upload my slides later.
00:17:10
Thank you for your time!