When dealing with complex applications, developers often encounter the challenge of managing deeply nested and immutable data structures. Traditional approaches can lead to verbose and error-prone code, making updates cumbersome and challenging to maintain. Enter Monocle-ts, a powerful library that brings functional optics to TypeScript, simplifying the manipulation of immutable nested objects.
In this blog, we'll explore how Monocle-ts can transform how you interact with data structures in TypeScript. Whether you're looking to update a nested company street name, handle an optional element, or compose multiple optics to zoom into your data, Monocle-ts provides a suite of tools to make these tasks elegant and type-safe. By the end of this post, you'll have a solid understanding of leveraging Monocle-ts to write cleaner, more maintainable code, propelling you toward becoming a senior engineer.
Monocle-ts is the TypeScript incarnation of the popular Scala library, Scala Monocle. It's designed to provide developers with abstractions known as optics, which allow for the immutable manipulation of data structures. The library embraces functional programming principles, offering a declarative approach to data transformation.
Optics are the heart of Monocle-ts, and there are three primary kinds you'll encounter:
One of the most powerful features of Monocle-ts is the ability to compose multiple optics together. This means you can zoom into a deeply nested structure with ease. For instance, if you have a company object with a nested address, and within that address, there's a street object with a street name, you can create two lenses: one for the company to address and another for address to street name, and then compose them to access and modify the street name directly.
Let's dive into a practical example. Suppose you have a company object with a nested address and want to update the street name. Here's how you can do it with a lens:
1import { lens, Lens } from 'monocle-ts'; 2 3interface Address { 4 street: string; 5} 6 7interface Company { 8 address: Address; 9} 10 11const companyStreetLens: Lens<Company, string> = lens( 12 (company) => company.address.street, 13 (street) => (company) => ({ ...company, address: { ...company.address, street } }) 14); 15 16const awesomeInc: Company = { address: { street: 'Old Street' } }; 17const updatedCompany = companyStreetLens.modify((street) => 'New Street')(awesomeInc); 18
In this example, we've created a lens that focuses on the street property within a company object. We then use the modify function to update the street name to 'New Street', resulting in a new company object with the updated address.
Handling optional values can be tricky, but prisms make it a breeze. Here's an example of using a prism to interact with an optional property safely:
1import { Prism } from 'monocle-ts'; 2import { some, none, Option } from 'fp-ts/lib/Option'; 3 4interface Employee { 5 id: number; 6 name: string; 7 nickname?: string; 8} 9 10const nicknamePrism = new Prism<Employee, string>( 11 (employee) => (employee.nickname ? some(employee.nickname) : none), 12 (nickname) => (employee) => ({ ...employee, nickname }) 13); 14 15const employee: Employee = { id: 1, name: 'John Doe' }; 16const updatedEmployee = nicknamePrism.modify((nickname) => 'Johnny')(employee); 17// If the nickname exists, it will be updated to 'Johnny'. If not, the original employee object is returned unchanged. 18
In this snippet, we've crafted a prism that targets the nickname property of an Employee object. The modify function is then used to update the nickname, but only if it exists, thanks to the safety provided by the Option type from the fp-ts library.
Arrays and nullable types are typical in any application, and Monocle-ts offers elegant solutions for these as well. Consider the following example where we need to update the first character of a string within an array of objects:
1import { lens, Lens } from 'monocle-ts'; 2import { array } from 'fp-ts/lib/Array'; 3 4interface Person { 5 name: string; 6} 7 8const firstCharacterLens: Lens<Person, string> = lens( 9 (person) => person.name[0], 10 (firstChar) => (person) => ({ ...person, name: firstChar + person.name.slice(1) }) 11); 12 13const people: Person[] = [{ name: 'Alice' }, { name: 'Bob' }]; 14const updatedPeople = array.modifyAt(0, firstCharacterLens.modify((char) => char.toUpperCase()))(people); 15
Here, we've created a lens to focus on the first character of a Person's name. We then use the array.modifyAt function from fp-ts to apply this lens to the first element of the people array, capitalizing the first character.
Sometimes, you'll encounter scenarios requiring custom optics tailored to your needs. For instance, ensure every company street name is in the upper case. Here's how you could achieve that with a custom lens:
1const upperCaseStreetLens: Lens<Company, string> = lens( 2 (company) => company.address.street.toUpperCase(), 3 (upperCasedStreet) => (company) => ({ ...company, address: { ...company.address, street: upperCasedStreet } }) 4); 5 6const companyWithUpperCaseStreet = upperCaseStreetLens.set('HIGH STREET')(awesomeInc); 7
In this example, we've defined an upperCaseStreetLens that not only accesses the street name but also transforms it to upper case.
While Monocle-ts is a powerful tool, it's essential to consider its impact on performance. Optics, mainly when composed, can introduce overhead. To mitigate this, ensure you only create and compose optics as needed and avoid unnecessary compositions. Reusing optics can also help in reducing the performance cost.
As with any library, there are best practices to follow when integrating Monocle-ts into your projects:
Monocle-ts offers robust tools for working with immutable data structures in TypeScript. By mastering lenses, prisms, and optionals, you can write more expressive, maintainable, and safer code. As you integrate Monocle-ts into your projects, remember to apply the best practices and keep performance in mind.
We encourage you to experiment with the examples provided and explore the Monocle-ts documentation for a deeper understanding. With Monocle-ts, you're well-equipped to tackle the complexities of immutable data manipulation and take a significant step forward in your journey as a developer.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.