When we write programs, it is common to need to keep track of some form of state during the execution of the program. State refers to any data that can change as the program runs. For example, in a counter application, the count value changes as the user increments or decrements it. Similarly, in a banking application, the account balance changes as deposits and withdrawals are made. State management is crucial to building interactive and dynamic applications.
In traditional imperative programming, one common way to store state is using variables. However, this approach can introduce bugs, especially when the state is shared between multiple components or functions. As the program becomes more complex, managing shared state can become challenging.
To overcome these issues, Effect introduces a powerful data type called Ref, which represents a mutable reference. With Ref, we can share state between different parts of our program without relying on mutable variables directly. Instead, Ref provides a controlled way to handle mutable state and safely update it in a concurrent environment.
Effect’s Ref data type enables communication between different fibers in your program. This capability is crucial in concurrent programming, where multiple tasks may need to access and update shared state simultaneously.
In this guide, we will explore how to use the Ref data type to manage state in your programs effectively. We will cover simple examples like counting, as well as more complex scenarios where state is shared between different parts of the program. Additionally, we will show how to use Ref in a concurrent environment, allowing multiple tasks to interact with shared state safely.
Let’s dive in and see how we can leverage Ref for effective state management in your Effect programs.
Using Ref
Here is a simple example using Ref to create a counter:
Example (Basic Counter with Ref)
Example (Using the Counter)
Using Ref in a Concurrent Environment
We can also use Ref in concurrent scenarios, where multiple tasks might be updating shared state at the same time.
Example (Concurrent Updates to Shared Counter)
For this example, let’s update the counter concurrently:
Using Ref as a Service
You can pass a Ref as a service to share state across different parts of your program.
Example (Using Ref as a Service)
Note that we use Effect.provideServiceEffect instead of Effect.provideService to provide an actual implementation of the MyState service because all the operations on the Ref data type are effectful, including the creation Ref.make(0).
Sharing State Between Fibers
You can use Ref to manage shared state between multiple fibers in a concurrent environment.
Example (Managing Shared State Across Fibers)
Let’s look at an example where we continuously read names from user input until the user enters "q" to exit.
First, let’s introduce a readLine utility to read user input (ensure you have @types/node installed):
Next, we implement the main program to collect names:
Now that we have learned how to use the Ref data type, we can use it to manage the state concurrently.
For example, assume while we are reading from the console, we have another fiber that is trying to update the state from a different source.
Here, one fiber reads names from user input, while another fiber concurrently adds preset names at regular intervals: