Moving your code towards a more functional style can have a lot of benefits – it can be easier to reason about, easier to test, more declarative, and more. One thing that sometimes comes out worse in the move to FP, though, is organization. By comparison, Object Oriented Programming classes are a pretty useful unit of organization – methods have to be in the same class as the data they work on, so your code is pushed towards being organized in pretty logical ways.
But where do you put them?
The first answer is often “at the bottom of the file.” For example, say you’ve got your main component class called UserComponent.js. You can imagine having a couple pure helper functions like fullName(user) at the bottom of the file, and you export them to test them in UserComponent.spec.js.
Then as time goes on, you add a few more functions. Now the component is a few months old, the file is 300 lines long and it’s more pure functions than it is component. It’s clearly time to split things up. So hey, if you’ve got a UserComponent, why not toss those functions into a
UserComponentHelpers.js? Now your component file looks a lot cleaner, just importing the functions it needs from the helper.
So far so good – though that
UserComponentHelpers.js file is kind of a grab-bag of functions, where you’ve got
fullName(user) sitting next to
And then you get a new story to show users’ full names in the navbar. Okay, so now you’re going to need that fullName function in two places. Maybe toss it in a generic utils file? That’s not great.
And then, a few months later, you’re looking at the
FriendsComponent, and find out someone else had already implemented
fullName in there. Oops. So now the next time you need a user-related function, you check to see if there’s one already implemented. But to do that, you have to check at least
FriendsComponent, and also
UserApiService, which is doing some User conversion.
So at this point, you may find yourself yearning for the days of classes, where a User would handle figuring out its own
fullName. Happily, we can get the best of both worlds by borrowing from functional languages like Elixir.
Modules in Elixir
Elixir has a concept called structs, which are dictionary-like data structures with pre-defined attributes. They’re not unique to the language, but Elixir sets them up in a particularly useful way. Files generally have a single module, which holds some functions, and can define a single struct. So a User module might look like this:
Even if you’re never seen any Elixir before, that should be pretty easy to follow. A User struct is defined as having a
last name, and
email. There’s also a related
full_name function that takes a
User and operates on it. The module is organized like a class – we can define the data that makes up a
User, and logic that operates on
Users, all in one place. But, we get all that without trouble of mutable state.
So, you can gather up all the user-related pure functions, from any component, and put them together in a
User.js file. That’s helpful, but both a class and an Elixir module define their data structure, as well as their logic.
User looks like in your system. But the problem with comments is they get out of date. That’s where something like TypeScript comes in. With TypeScript, you can define an interface, and the compiler will make sure it stays up-to-date:
This also works great with propTypes in react. PropTypes are just objects that can be exported, so you can define your User propType as a
PropType.shape in your User module.
Then you can use the User’s type and functions in your components, reducers, and selectors.
You could do something very similar with Facebook’s Flow, or any other library that lets you define the shape of your data.
However you define your data, the key part is to put a definition of the data next to the logic on the data in the same place. That way it’s clear where your functions should go, and what they’re operating on. Also, since all your user-specific logic is in once place, you’ll probably be able to find some shared logic to pull out that might not have been obvious if it was scattered all over your codebase.
It’s good practice to always put the module’s data type in a consistent position in your functions – either always the first parameter, or always the last if you’re doing a lot of currying. It’s both helpful just to have one less decision to make, and it helps you figure out where things go – if it feels weird to put user in the primary position, then the function probably shouldn’t go into the User module.
Functions that deal with converting between two types – pretty common in functional programming – would generally go into the module of the type being passed in –
userToFriend(user, friendData) would go into the
User module. In Elixir it would be idiomatic to call that
User.to_friend, and if you’re okay with using wildcard imports, that’ll work great:
userToFriend would be more clear:
Consider wildcard imports
However, I think that with this functional module pattern, wildcard imports make a lot of sense. They let you prefix your functions with the type they’re working on, and push you to think of the collection of User-related types and functions as one thing like a class.
But if you do that and declare types, one issue is that then in other classes you’d be referring to the type
User.userType. Yuck. There’s another idiom we can borrow from Elixir here – when declaring types in that language, it’s idiomatic to name the module struct’s type
We can replicate that with React PropTypes by just naming the propType
t, like so:
It also works just fine in TypeScript, and it’s nice and readable. You use t to describe the type of the current module, and
Module.t to describe the type from Module.
t in TypesScript does break a popular rule from the TypeScript Coding Guidelines to “use PascalCase for type names.” You could name the type
T instead, but then that would conflict with the common TypeScript practice of naming generic types
User.t seems like a nice compromise, and the lowercase
t feels like it keeps the focus on the module name, which is the real name of the type anyway. This is one for your team to decide on, though.
Decoupling your business logic from your framework keeps it nicely organized and testable, makes it easier to onboard developers who don’t know your specific framework, and means you don’t have to be thinking about controllers or reducers when you just want to be thinking about users and passwords.
This process doesn’t have to happen all at once. Try pulling all the logic for just one module together, and see how it goes. You may be surprised at how much duplication you find!
So in summary:
Try organizing your functional code by putting functions in the same modules as the types they work on.
Put the module’s data parameter in a consistent position in your function signatures.
import * as Module wildcard imports, and naming the main module type