Primitive Functors

Mike Schutte
6 min readNov 30, 2017

--

Spreading the .map() love to other data types

three important keys!

If you’re like me, you’ve been a little intrigued by functional programming concepts. Hearing descriptions like “more pure” and “mathematically sound” are hard headlines to ignore.

If you’re like me, you’ve also had several go-rounds trying to comprehend what the $%^* functors and monads and all that jazz are. While I still have a long way to go, I want to share a recent bit of clarity I found with respect to functors.

Have I used these on the job? Nope. Do I fully understand when they’d be legitimate advantage over other options? Strike two. I’m just really excited about making some progress on this learning journey, and I want to share it with other people early in the FP quest.

Let’s say we have some raw user data from a fetch to an API (which here refers to an external data source). What we get back is:

const rawUsers = [
{
name: "Mike",
age: 26,
},
{
name: "Hannah",
age: 25,
},
]

Descriptive. I know. And no: I didn’t forget a semi-colon 😉.

There is something about transforming objects and arrays that I find endlessly fascinating. Give me an opportunity to chain filter ,map , and reduce together, and you’ve given me a box of legos.

If I want just the names from this user data, I can run

const names = rawUsers.map(user => user.name)

It is downright pleasant to read that simple transformation. Functional storytelling at its finest. I want to make a new collection of information based on an original collection of users. That new collection consists only of the values of each name property.

There are certainly other ways to get the desired result of ['Mike', 'Hannah'], but I’m a sucker for a pleasant API (which here means the syntax and structure I can use in the program).

If you’re unfamiliar with running map on arrays, go here and here (NOW)and have some fun. See you in a little bit.

I dragged you through that map example to set the stage. A functor is a data type (let’s call it F) that implements map . map may have synonyms in other languages, but it represents a higher order function that, given a function as an argument, applies that function to the value(s) associated with F.

A functor also has to have a function that creates instances of its data type. This is often called unit . No matter what it is called, a functor has to have a way to make new functors of the same type. There are also specific laws of category theory that a functor must obey, but let’s focus on the basics (see here and here for more information).

map -ing over an array is built into JavaScript, as it is a common task. But what if I want to use that same lovely API (meaning how I get to program again) for transforming data on a single value? I don’t want to just put the value in an array because it might not truly represent the data I’m trying to describe. Lists are lists and an element is an element.

What if I want to map over the users again, but transform each name to lower case? I could do something like

const lowerNames = rawUsers.map(user => ({
name: user.name.toLowerCase(),
age: user.age,
})

But I feel conflicted because I have one style of transformation ( map ) mixed with another (chaining together properties and methods). I wish I could call .map on name to have a similar API (how I get to program) throughout…

rawUsers , meet thePrimitive functor! 🎉 🤝 🎉

const Primitive = value => ({
map: fn => Primitive(fn(value)),
valueOf: () => value,
})

Here we have a function assigned to a const named Primitive . It takes on argument, value, and returns an object with two methods: map and valueOf . Matching the description from above, map takes a function as an argument, and returns a new Primitive type with the new value equal to what the function applied to the original value returns. valueOf is just a nice getter method that returns that value. Because Primitive is a function, it serves as our unit tool that can make object instances of this particular shape and behavior.

Let’s map our rawUsers to a collection of users with Primitive wrappers on name and age . First I want a function that I can pass to rawUsers.map() that transforms name and age to Primitive -wrapped forms.

const makeFUNctorUser = ({ name, age }) => ({
name: Primitive(name),
age: Primitive(name),
})

We could also abstract this further, decoupling from the name and age interface using reduce :

const makeFUNctorUser = user => Object.keys(user).reduce((acc, prop) => ({
...acc,
[prop]: Primitive(user[prop])
}), {})

This is more flexible to change over time. If the database adds profilePictureUrl and address to the User table, for example, the first makeFUNctorUser would have to change, the second wouldn’t.

Back to map -ing rawUsers to functorUsers . Using ourmakeFUNctorUser function:

const functorUsers = rawUsers.map(user => makeFUNctorUser(user))/*
[ { name: { map: [Function: map], valueOf: [Function: valueOf] },
age: { map: [Function: map], valueOf: [Function: valueOf] } },
{ name: { map: [Function: map], valueOf: [Function: valueOf] },
age: { map: [Function: map], valueOf: [Function: valueOf] } } ]
*/

I once wished I could call .map on name instead of chaining methods onto it…remember? Now we can! To get a new collection of users with their names in lower case:

const lowerCase = string => string.toLowerCase()const lowerNameUsers = functorUsers.map(user => ({
...user,
name: name.map(lower),
})
lowerNameUsers.map(({ name }) => name.valueOf())
// [ 'mike', 'hannah' ]

I hope you are rolling your eyes 🙄 at all the effort I put into getting an array of lowercase names. It’s more abstracted and downright. more. code.

You aren’t wrong, and if this doesn’t tickle your fancy, I don’t blame you. What I like about this is that I can abstract away the implementation details of my desired transformations, which opens me up to use them in other contexts.

Saw we have a more complicated requirement: reverse the names!

const reverse = string => string.split('').reverse().join('')

The clunky implementation details only need to be written once, I can just use somePrimitiveWrappedString.map(reverse)ad infinitum. For simple cases like this, you could rightfully point out that reverse(someString) is about as much code as someFUNctorString.map(reverse)). Why go through all the extra effort? Again: you are right! I’m drawn to this extra work for three reasons.

  1. I prefer the story of “transform this data to something else with this function” over the story of “take this function and this input and give me the return value”. While abstractions are less concrete by definition, I often feel the API is more intuitive and represents my actual thought patterns.
  2. When a primitive type like a string is wrapped by functor likePrimitive , we can add to and compose more behavior for these simple data types in an elegant way and have just a big ol’ map party.
  3. In reverse(someString), reverse is the subject and someString the object of the “functional sentence”. In someFUNctorString.map(reverse) , someFUNctorString is the start of the show, and reverse is just a fun toy that the star can play with. So much of programming is just transforming data from one shape/type/structure to another in order to tell compelling stories or facilitate engaging experiences. In my opinion, it’s the strings, numbers, and lists that really matter to the end user, not the functions we use to get the data to the desired state. Therefore, I like treating the data structures as the main “actors” in the program, and the functions are these nifty little props and tools that allow the actors (and directors, producers, crew, &c) to deliver a great show.

I hope that implementing Primitive helped clarify what functors are and what they do. Functors are data types that wrap value(s) and implement amap or a map-like method to transform the data type’s value(s). While the internal values can be transformed by the function provided to map, the wrapping functor type stays the same. Functors also need an interface for making new instances of the data type. In our case, Primitive was a factory function that also represented a data type.

The next time you find yourself frustrated with transforming primitive data types, reach for a handy-dandy functor.

Mike Schutte is a developer at Quikly in Detroit, Michigan. He loves programming in Ruby and JavaScript, and is constantly trying to negotiate the awkward landscape between OO and FP.

--

--

Mike Schutte

Licensed Driver 🚗 TSA Pre ® AeroPress Barista ☕️ Conversational in Emoji 🤟 Anti-mouse (💻✚🏠) Pro-listening 👂he/him 👨‍💻 @TEDTalks