in ,

The Algebraic Structure of Functions, illustrated using React components, Hacker News

The Algebraic Structure of Functions, illustrated using React components, Hacker News
          


Did you know there’s an algebraic structure for functions? That may not surprise you at all. But it surprised me when I first found out about it. I knew we used functions to build algebraic structures. It never occurred to me that functions themselves might have an algebraic structure.

I should clarify though. When I use the word ‘function’ here, I mean function in the functional programming sense. Not in the JavaScript sense). That is, pure functions; no side effects; single input; always return a value; and so on… You know the drill. Also, I’m going to assume you understand referential transparency and composition. If not, check out A gentle introduction to functional JavaScript . It might also help if you’ve read How to deal with dirty side effects in your pure functional JavaScript .

How does this algebraic structure for functions work? Well, recall our idea of ​​ eventual numbers when we looked at Effect . They looked something like this:

  const compose2=f=> g=> x=> f (g (x)) ; const increment=x=> x   1; const double=x=> x 2;  const zero=()=> 0; const one=compose2 (increment) (zero); const two=compose2 (double) (one); const three=compose2 (increment) (two); const four=compose2 (double) (two); // ... and so on. 

In this way we could create any integer as an eventual integer. And we can always get back to the ‘concrete’ value by calling the function. If we call three ()

 at some point, then we get back 3. But all that composition is a bit fancy and unnecessary. We could write our eventual values ​​like so: 

  const zero=()=> 0; const one=()=> 1; const two=()=> 2; const three=()=> 3; const four=()=> 4;  // ... and so on. 

Looking at it this way may be a little tedious, but it’s not complicated. To make a delayed integer, we take the value we want and stick it in a function. The function takes no arguments, and does nothing but return our value. And we don’t have to stop at integers. We can make any value into an eventual value. All we do is create a function that returns that value. For example:

  const ponder=()=> 'Curiouser and curiouser'; const pi=()=> Math.PI; const request=()=> ({     protocol: 'http',     host: 'example.com',     path: '/ v1 / myapi',     method: 'GET' });  // You get the idea ... 

Now, if we squint a little, that looks kind of like we’re putting a value inside a container. We’ve got a bit of containery stuff on the left, and value stuff on the right. The containery stuff is uninteresting. It’s the same every time. It’s only the return value that changes.

Enter the functor

Could we make a Functor out of this containery eventual-value thing? To do that, we need to define a law-abiding map ()

 function. If we can, then we’ve got a valid functor on our hands. 

To start, let’s look at the type signature for map () . In Hindley-Milner notation, it looks something like this:

map :: Functor m=> (a -> b) -> m a -> m b

This says that our map function takes a function, and a functor of a , and returns a functor of b . If functions are functors, then they would go into that m

 slot: 

map :: (a -> b) -> Function a -> Function b

This says that map () takes a function from a ( b

 and a Function of  (a) . And it returns a Function of  b . But what's a 'Function of  a 
) or a' Function of  b ? 

What if we started out with eventual values? They’re functions that don’t take any input. But they return a value. And that value (as we discussed), could be anything. So, if we put them in our type signature might look like so:

map :: (a -> b) -> (() -> a) -> (() -> b)

The a and

b in the type signature are return value of the function. It’s like map () doesn’t care about the input values. So let’s replace the ‘nothing’ input value with another type variable, say t

. This makes the signature general enough to work for any function. 

map :: (a -> b) -> (t -> a) -> (t -> b)

If we prefer to work with a

,  b  and  c 

, it looks like this:

map :: (b -> c) -> (a -> b) -> (a -> c)

And that type signature looks a lot like the signature for compose2 :

  compose2 :: (b -> c) -> (a -> b) -> a -> c 

And in fact, they are the same function. The map () definition for functions is composition.

Let's stick our map () function in a

Static-Land module

and see what it looks like:

 
 const Func={     map: f=> g=> x=> f (g (x)), }; 

And what can we do with this? Well, no more and no less than we can do with compose2 ()

. And I assume you already know many wonderful things you can do with composition. But function composition is pretty abstract. Let’s look at some more concrete things we can do with this. 

React functional components are functions

Have you ever considered that React functional components are genuine, bona fide functions? (Yes, yes. Ignoring side effects and hooks for the moment). Let’s draw a couple of pictures and think about that. Functions in general, take something of type (A ) and transform it into something of type (B ) . A function takes an input of some type A and returns an output value of some type B. A function takes an input of some type A and returns an output value of some type B.

I'm going to be a bit. sloppy with types here but bear with me. React functional components are functions, but with a specific type. They take Props and return a Node. That is, they take a JavaScript object return something that React can render. (1) So that might look something like this:

A function takes an input of some type A and returns an output value of some type B. Now consider map ()

 /  compose2 ()  . It takes two functions and combines them. So, we might have a function from type   (B )  (to   (C )  and another from   (A )  (to   (B ) . We compose them together, and we get a function from   (A )  (to 

)
. We can think of the first function as a modifier function that acts on the output of the second function.

A function takes an input of some type A and returns an output value of some type B.

Let's stick a React functional component in there. We’re going to compose it with a modifier function. The picture then looks like this:

A function takes an input of some type A and returns an output value of some type B.

Our modifier function has to take a Node as its input. Otherwise, the types don’t line up. That’s fixed. But what happens if we make the return value Node as well? That is, what if our second function has the type (Node rightarrow Node ) ?

A function takes an input of some type A and returns an output value of some type B.

We end up with a function that has the same type as a React Function Component . In other words, we get another component back. Now, imagine if we made a bunch of small, uncomplicated functions. And each of these little utility functions has the type (Node rightarrow Node ) . With map ()

 we can combine them with components, and get new, valid components . 

Let’s make this real. Imagine we have a design system provided by some other team. We don’t get to reach into its internals and muck around. We’re stuck with the provided components as is. But with map () we claw back a little more power. We can tweak the output of any component. For example, we can wrap the returned Node with some other element:

Import React from 'react'; import AtlaskitButton from '@ atlaskit / button'; // Because Atlaskit button isn't a function component, // we convert it to one. const Button=props=> (); const wrapWithDiv=node=> (

{node} ); const WrappedButton=Func.map (wrapWithDiv) (Button);

A function takes an input of some type A and returns an output value of some type B. See it in a sandbox

Or we could even generalise this a little…

Import React from "react"; import AtlaskitButton from "@ atlaskit / button"; // Because Atlaskit button isn't a function component, // we convert it to one. const Button=props=> ; const wrapWith=(Wrapper, props={})=> node=> (      {node} ; const WrappedButton=Func.map (   wrapWith ("div", {style: {border: "solid pink 2px"}}) ) (Button);

See it in a sandbox

What else could we do? We could append another element:

Import React from "react"; import AtlaskitButton from "@ atlaskit / button"; import PremiumIcon from "@ atlaskit / icon / glyph / premium"; // Because Atlaskit button isn't a function component, // we convert it to one. const Button=props=> ; const appendIcon=node=> ( {node}

); const PremiumButton=Func.map (appendIcon) (Button);

> See it in a sandbox

Or we could prepend an element:

Import React from 'react'; import Badge from '@ atlaskit / badge'; const prependTotal=node=> (
Total: {node} ) const TotalBadge=Func.map (prependTotal) (Badge);

See it in a sandbox

And we could do both together:

Import React from 'react'; import StarIcon from '@ atlaskit / icon / glyph / star'; import Button from '@ atlaskit / button'; // Because Atlaskit button isn't a function component, // we convert it to one. const Button=props=> ; const makeShiny=node=> (               {node }     ; const ShinyButton=Func.map (makeShiny) (Button);

See it in a sandbox

And all three at once:

Import React from 'react'; import AtlaskitButton from "@ atlaskit / button"; import Lozenge from '@ atlaskit / lozenge'; import PremiumIcon from '@ atlaskit / icon / glyph / premium'; import Tooltip from '@ atlaskit / tooltip'; // Because Atlaskit button isn't a function component, // we convert it to one. const Button=props=> ; const shinyNewThingify=node=> (                       {node}          (New)      ; const ShinyNewButton=Func.map (shinyNewThingify) (Button); const App=()=> (      (Runcible Spoon) );

See it in a sandbox Element enhancers

I call these (Node rightarrow Node ) functions Element enhancers . () (2) It's like we're creating a template. We have a JSX structure with a node-shaped hole in it. We can make that JSX structure as deep as we like. Then, we use Func.map () to compose the element enhancer with a component . We get back a new component that eventually shoves something deep down into that slot. But this new component takes the same props as the original.

This is nothing we couldn’t already do. But what’s nice about element enhancers is their simplicity and re-usability. An element enhancer is a simple function. It doesn’t mess around with props or anything fancy. So it’s easy to understand and reason about. But when we map ()

 them, we get full-blown components. And we can chain together as many enhancers as we like with  map () 
. 

I have a lot more to say about this, but I will save it for another post. Let’s move on and look at Contravariant Functors.

Contravariant functor

Functors come in lots of flavors. The one we’re most familiar with is the covariant functor. That’s the one we’re talking about when we say ‘functor’ without any qualification. But there are other kinds. The contravariant functor defines a contramap ()

 function. It looks like someone took all the types for  map () 

and reversed them:

- - Functor general definition map :: (a -> b) -> Functor a -> Functor b - Contravariant Functor general definition contramap :: (a -> b) -> Contravariant b -> Contravariant a - Functor for functions map :: (b -> c) -> (a -> b) -> (a -> c) - Contravariant Functor for functions contramap :: (a -> b) -> (b -> c) -> (a -> c)

Don’t worry if none of that makes sense yet. Here's how I think about it. With functions, map () let us change the output of a function with a modifier function. But contramap () lets us change the input of a function with a modifier function. Drawn as a diagram, it might look like so:

A function takes an input of some type A and returns an output value of some type B.

If we're doing this with React components then it becomes even clearer. A regular component has type (Props rightarrow Node )

. If we stick a (Props rightarrow Props ) function in front of it, then we get a (Props rightarrow Node ) function back out. In other words, a new component.

A function takes an input of some type A and returns an output value of some type B.

So, contramap () is (map () with the parameters switched around:

 
 const Func={     map: f=> g=> x=> f (g (x)),     contramap: g=> f=> x=> f (g (x)), }; 

Contramapping react functional components

What can we do with this? Well, we can create functions that modify props. And we can do a lot with those. We can, for example, set default props:

// Take a button and make its appearance default to 'primary' import Button from '@ atlaskit / button'; function defaultToPrimary (props) {     return {appearance: 'primary', ... props}; } const PrimaryButton=Func.contramap (defaultToPrimary) (Button);

See it in a sandbox

And, of course, we could make a generic version of this:

Import Button from '@ atlaskit / button'; function withDefaultProps (defaults) {     return props=> ({... defaults, ... props}); } const PrimaryButton=Func.contramap (     withDefaultProps ({appearance: 'primary'}) ) (Button);

See it in a sandbox

If we want to, we could also hard-code some props so that nobody can change them. To do that we reverse our spread operation.

Import Button from '@ atlaskit / button'; function withHardcodedProps (fixedProps) {     return props=> ({... props, ... fixedProps}); } const PrimaryButton=Func.contramap (     withHardcodedProps ({appearance: 'primary'}) ) (Button);

See it in a sandbox

You might be thinking, is that all? And it might not seem like much. But modifying props gives us a lot of control. For example, remember that we pass children as props. So, we can do things like wrap the inner part of a component with something. Say we have some CSS:

spacer {     padding: 0. 823 rem; }

And imagine we’re finding the spacing around some content too tight. With our handy tool contramap () , we can add a bit of space:

Import React from 'react'; import AtlaskitSectionMessage from '@ atlaskit / section-message'; // Atlaskit's section message isn't a functional component so // we'll convert it to one. const SectionMessage=props=> ; const addInnerSpace=({children, ... props})=> ({     ... props,     children: ({children}

}); const PaddedSectionMessage=Func.contramap (addInnerSpace) (SectionMessage); const App=()=> (              

        The Lion and the Unicorn were fighting for the crown:         The Lion beat the Unicorn all round the town.

        Some gave them white bread, some gave them brown:         Some gave them plum-cake and drummed them out of town.              ;

(See it in a sandbox Functions as profunctors

Our contramap () function lets us change the input and map () lets us change the output. Why not do both together? This pattern is common enough that it has a name: promap ()

. And we call structures that you can  promap ()  over,  profunctors . Here's a sample implementation for  promap () : 

 
 const Func={     map: f=> g=> x=> f (g (x)),     contramap: g=> f=> x=> f (g (x)),     promap: f=> g=> h=> Func.contramap (f) (Func.map (g) (h)), }; 

Here's an example of how we might use it:

Import React from "react"; import AtlaskitTextfield from "@ atlaskit / textfield"; // Atlaskit's Textfield isn't a function component, so we // convert it. const Textfield=props=>

; const prependLabel=(labelTxt, id)=> node=> (         {labelTxt}     {node}   ; function withHardcodedProps (fixedProps) {   return props=> ({... props, ... fixedProps}); } const id="thamaturgical-identifier"; const lblTxt="Please provide your thaumaturgical opinion:"; const ThaumaturgyField=Func.promap (withHardcodedProps ({id})) (   prependLabel (lblTxt, id) ) (Textfield); export default function App () {   return (                    ); }

(See it in a sandbox)

With promap () we could tweak the props and the output of a React component in one pass. And this is pretty cool. But what if we wanted to change the output based on something in the input? The sad truth is that promap () can't help us here.

(Functions as applicative functors)

All is not lost. We have hope. But first, why would we want to do this? Let’s imagine we have a form input. And rather than disable the input when it’s not available, we’d like to hide it entirely. That is, when the input prop disabled is true , then we don't render the input at all. To do this, we’d function that has access to both the input and the output of a component. So, what if we passed the input (props) and output (node) as parameters? It might look like so:

// hideWhenDisabled :: Props -> Node -> Node const hideWhenDisabled=props=> node=> (     (props.isDisabled)? null: node );

Not all that complicated. But how do we combine that with a component? We need a function that will do two things:

Take the input (props) and pass it to the component; and then,

Pass both the input (props) and output (node) to our (hideWhenDisabled) function.

It might look something like this:

// mysteryCombinatorFunction :: (a -> b -> c) -> (a -> b) -> a -> c const mysteryCombinatorFunction=f=> g=> x=> f (x) (g (x));

And this mystery combinator function has a name. It’s called ap () . Let's add ap ()

 to our  Func  module: 

 
 const Func={     map: f=> g=> x=> f (g (x)),     contramap: g=> f=> x=> f (g (x)),     promap: f=> g=> h=> Func.contramap (f) (Func.map (g) (h)),     ap: f=> g=> x=> f (x) (g (x)), }; 

Here's how it might look as a diagram:

A function takes an input of some type A and returns an output value of some type B. (

If we are working with react components, then it might look like so:

A function takes an input of some type A and returns an output value of some type B.

With that in place, we can use our hideWhenDisabled ()

function like so: Import React from "react"; import AtlaskitTextfield from "@ atlaskit / textfield"; // Atlaskit's Textfield isn't a function component, so we // convert it. const Textfield=props=> ; // hideWhenDisabled :: Props -> Node -> Node const hideWhenDisabled=props=> el=> (props.isDisabled? null: el); const DisappearingField=Func.ap (hideWhenDisabled) (Textfield);

See it in a sandbox

Now, for a function to be a full applicative functor, there’s another function we need to implement. That’s of () . It takes any value and turns it into a function. And we’ve already seen how to do that. It’s as simple as making an eventual value:

// Type signature for of (): // of :: Applicative f=> a -> f a // For functions this becomes: // of :: a -> Function a // Which is the same as: // of :: a -> b -> a // We don’t care what the type of b is, so we ignore it. const of  =x=> ()=> x;

Let’s stick that in our module:

 
 const Func={     map: f=> g=> x=> f (g (x)),     contramap: g=> f=> x=> f (g (x)),     promap: f=> g=> h=> Func.contramap (f) (Func.map (g) (h)),     ap: f=> g=> x=> f (x) (g (x)),     of: x=> ()=> x, }; 

There's not much advantage in using Func.of () over creating an inline function by hand. But it allows us to meet the specification. That, in turn, means we can take advantage of derivations and pre-written code. For example, we can use ap ()

 and  of ()  to derive  map () :  
  const map=f=> g=> Func.ap (Func.of (f) ) (g); 

Not all that useful, but good to know.

(Functions as monads)

One final thought before we wrap up. Consider what happens if we swap the parameter order for our hideWhenDisabled ()

 function. It might look something like this: 

hideWhenDisabledAlt :: Node -> Props -> Node const hideWhenDisabledAlt=el=> props=> (     props.isDisabled? null: el );

The inside of the function doesn’t change at all. But notice what happens if we partially apply the first parameter now:

Import TextField from '@ atlaskit / textfield'; // hideWhenDisabledAlt :: Node -> Props -> Node const hideWhenDisabledAlt=el=> props=> (     props.isDisabled? null: el ); const newThing=hideWhenDisabled ();

What's the type of newThing

?   

That’s right. Since we've filled that first Node slot, the type of newThing

 is   (Props  rightarrow Node ) 

. The same type as a component. We’ve created a new component that takes just one prop: isDisabled . So, we can say that hideWhenDisabledAlt () is a function that takes a Node and returns a Component.

That’s pretty cool all by itself. But we can take this one step further. What if we could chain together functions like this that returned components? We already have map () which lets us shove a component into an element enhancer. What if we could do a similar thing and jam components into functions that return components?

As it so happens, this is what the monad definition for functions does. We define a chain () function like so:

// Type signature for chain in general: // chain :: Monad m=> (b -> m c) -> m b -> m c // Type signature for chain for functions: // chain :: (b -> Function c) -> Function b -> Function c // Which becomes: // chain :: (b -> a -> c) -> (a -> b) -> a -> c const chain=f=> g=> x=> f (g (x)) (x);

Drawn as a diagram, it might look something like this:

A function takes an input of some type A and returns an output value of some type B.A function takes an input of some type A and returns an output value of some type B.

And here's how it looks inside our


. Func

 module: 

 
 const Func={     map: f=> g=> x=> f (g (x)),     contramap: g=> f=> x=> f (g (x)),     promap: f=> g=> h=> Func.contramap (f) (Func.map (g) (h)),     ap: f=> g=> x=> f (x) (g (x)),     of: x=> ()=> x,     chain: f=> g=> x=> f (g (x)) (x),     flatMap: Func.chain, }; 

I like to add flatMap () as an alias to chain () . Naming it flatMap () makes more sense and is contsistent with Array.prototype.flatMap ()

. But, chain () is what we have in the specification. And, to be fair, Brian wrote the Fantasy Land spec before flatMap () for arrays existed.

If we substitute the component type into our diagram above, then it looks like so:

A function takes an input of some type A and returns an output value of some type B.

What can we do with (chain)

 /  (flatMap)  We can take a bunch of functions that return components and chain them together. For example:   
  Import Modal, {ModalTransition} from '@ atlaskit / modal-dialog';  // compose :: ((a -> b), (b -> c), ..., (y -> z)) -> a -> z const compose=(... fns)=> (... args)=>   fns.reduceRight ((res, fn)=> [fn.call(null, ...res)], args) [0];  const wrapInModal=inner=> ({onClose, actions, heading})=> (        {inner}    );  const showIfOpen=inner=> ({isOpen})=> isOpen &&  {inner} ;  const withModalTransition=el=>  {el} ;  const modalify=compose (   Func.map (withModalTransition),   Func.chain (showIfOpen),   Func.chain (wrapInModal), ); 

We now have a function

 modalify () 
, that will take any  Component  and place it inside a modal. Not any  Element  or  Node . No, any  Component . As a consequence, our new ‘modalified’ component will take four extra props. They are  actions ,  isOpen ,  onClose   and  heading . These control the appearance of the modal. But, the way it’s written now, it will pass those to the inner component too. We can prevent that with a prop modifier:    const withoutModalProps=({actions, isOpen, onClose, heading, ... props})=>   props;  const modalify=compose (     Func.map (withModalTransition),     Func.chain (showIfOpen),     Func.chain (wrapInModal),     Func.contramap (withoutModalProps), ); 

See it in a sandbox

Now, this perhaps isn’t the best example. It will probably be more familiar to most people if we write this out using JSX:

  const modalify=Component=> ({actions, isOpen, onClose, heading, ... props})=> (              {isOpen && (                                                    )}      ); 

But why?

Let me ask you a question. We have two versions of the same modalify () function above. One written with composition, the other with plain JSX. Which is more reusable?

It’s a trick question. The answer is neither. They’re the same function. Who cares whether it's written with composition or JSX? As long as their performance is roughly the same, it doesn’t matter. The important thing is that we can write this function at all . Perhaps you are more clever than I am. But it never would have occurred to me to write a modalify ()

 function before this . Working through the algebraic structure opens up new ways of thinking. 

Now, someone might be thinking: “But this is just higher-order components (HOCs). We’ve had those for ages. ” And you’d be correct. The React community has been using HOCs for ages. I’m not claiming to introduce anything new here. All I’m suggesting is that this algebraic structure might provide a different perspective.

Most HOCs tend to be similar to our modalify () example. They take a component, modify it, and give you back a new component. But the algebraic structure helps us enumerate all the options. We can:

(Modify Nodes (elements) returned from a Component with map () ; Modify Props going into a Component with contramap () (Do both at the same time with) promap () ;

Modify Nodes based on values ​​in Props with ap ()

; and   Chain together functions that take a Node and return a Component with  chain () 
 (aka  flatMap () 

And no, we don't need promap () or (ap ()

 or  chain () 
 to do any of these things. But when we do  reuse  in React, we tend to think  only  of Components.  Everything is a component  is the mantra. And that’s fine. But it can also be limiting. Functional programming offers us so many ways of combining functions. Perhaps we could consider reusing functions as well.   

Let me be clear. I'm not suggesting anyone go and write all their React components using compose

,  (map () , and  chain ()  I’m not even suggesting anyone include a  Func  library in their codebase. What I am hoping is that this gives you some tools to think differently about your React code. I’m also hoping that the algebraic structure of functions makes a little more sense now. This structure is the basis for things like the Reader monad and the State monad. And they’re well worth learning more about. 

        


Read More

What do you think?

Leave a Reply

Your email address will not be published. Required fields are marked *

GIPHY App Key not set. Please check settings

Dow Futures Plunge after CDC's Chilling New Coronavirus Warning, Crypto Coins News

Dow Futures Plunge after CDC's Chilling New Coronavirus Warning, Crypto Coins News

Mark Cuban: Bitcoin benefits from the US ‘printing so much money’