The art of technology

Blog

Back

Funcional programming in TypeScript

Technology
Funcional programming in TypeScript

Over the last two years, we in our company (back-end written with a combination of Node.js and TypeScript) have started to look for a kind of semi-functional concept of writing a code.

In part, we were probably hit by a wave of functional hype, which I dare to say, the increasingly powerful React community began to push into the real mainstream. And in part, it simply made sense in the ecosystem of Node.js, TypeScript, GraphQL, and microservices.

I would like to point out that the vast majority of the team comes from the world of those classic enterprise technologies such as Java / Spring, typically used more in the context of OOP, DI, etc.

The term more functional style of writing code is intentionally written, because it may not be (for a conceited academic!) Strictly accurate FP such as Haskell (?). But let's say that on the imaginary curve of the functional TypeScript we can move from helper libraries (Ramda, Lodash, etc.) to a complex system of the fp-ts type.

But what does functional mean? And how do you actually define such a style of writing / thinking?

But first, a small, simplified glossary of terms:

  • algebraic structure - canonical object with different complexity and prescribed behavior (laws) - structures as monoid, functor, applicative - imagine as a prescription for an object with expected behavior, a kind of design pattern in FP
  • category - object and morphism - in TypeScript it is possible to express morphism as f: A => B, when f expresses morphism from type A to type B - basically a couple of object (type): function
  • HKT - higher kinded types - a type parameter that accepts another type as an argument - a generic (previously unknown) type can accept another type as a parameter - HCTs are not part of TS by default - sometimes we need to perform operations on a generic structure and that unknown structure to install a hitherto unknown type
  • currying - <T> (a, b, c) => T aka <T> (a) => (b) => c => T
  • partial application - <T> (a, b, c) => T aka <T> (a, b) => c => T
  • functorial - for a start, imagine an algebraic structure that is able to take over another structure, expand its contents, apply a function (map) to it and repackage the result in some envelope - it can perform mapping between categories
  • monad - a more complex algebraic structure than a functorial - in contrast to the functorial, it can, for example, apply a function hidden in another envelope to the data in the envelope

I will try to avoid academic descriptions from Wikipedia. Every ninny from PHP to Angular knows that a monad is a monoid in the category of endofunctors :-). So I will try to share a few notes from practice (not the definition of functional programming), and avoid phrases of the type "declarative vs ...", because declarative today is simply where is what.

We think more about the structures that manipulate data than about the data as such

I will try to sketch a simple example, but it is necessary to realize that this is a bit illustrative, because the core of thinking about FP structures can be a level more abstract - in algebraic structures.

interface Washable {
  wash: () => ...,
  clean: () => ...,
}

interface Openable {
  open: () => ...,
  close: () => ...,
}

const vehicle: Washable & Openable = {
  wash: () => ...,
  clean: () => ...,
  open: () => ...,
  close: () => ...,
}

const table: Washable= {
  wash: () => ...,
  clean: () => ...,
}

const tableWithDoors: Washable & Openable = {
  wash: () => ...,
  clean: () => ...,
  open: () => ...,
  close: () => ...,
}

const openAndWash = (entity: Washable & Openable) = {
  return entity.open().then(res => res.wash());
}

openAndWash(vehicle); 
openAndWash(tableWithDoors); 

// bad
openAndWash(table);

As can be seen from the picture, the openAndWash function does not care whether it opens the door at the vehicle or from the table, only the reliability of the structure is important - every Washable structure is able to apply clean. Just as a functorial is able to take any data from a wrapper, apply a function to it, and repack it in an envelope. And that is the canon.

That dividing line may seem insignificant but thinking "data agnostic" makes an order of magnitude greater sense when we begin to look at a more abstract structure that does not imply the nature of the data at all but offers only different ways of manipulating another structure.

If we imagine a structure of the functorial type behind the Washable type - which, like Washable, is able to manipulate data through the expected pattern, we get a little closer to the poodle's core.

We will talk about algebraic structures (functorials, monads) and more complex types in a future blog. However, the message is as follows: a functionally tuned programmer thinks more about the relationship between the structures that manipulate the data than about the data.

We chain, we chain!

const cleanMyCar = <A>(car:A) => pipe(
    car,
    openDoor,
    cleanDoor,
    closeDoor,
    cleanWindow,
    (car) => `${car} is now clean!`
)
const cleanMyCar = <A>(car:A) => flow(
    openDoor,
    cleanDoor,
    closeDoor,
    cleanWindow,
    (car) => (`${car} is now clean!`)
)(car)

The picture is quite descriptive - if something (not only) is visually typical for FP, it is the chaining of functions into a gradually connected whole or the composition of simpler functions into more complex ones.

The use of different pipes may seem interchangeable with the classic declaration of variables, but I have noticed that it leads (guides, does not directly imply) the following:

  • More appropriate function naming - concatenated functions call directly for accurate and descriptive naming
  • Shorter functions and simpler functions - concatenated functions in combination with naming call for a single responsibility principle / single purpose function - no multipurpose master functions
  • The record is clear and clearly declares the flow of events
  • Functions in JS / TS are so-called first-class objects - it is possible to use them as return values and parameters of other functions - which is in combination with currying or partial application, a very complex tool for flexible concatenation

Composition over inheritance

The ancient mantra certainly does not apply only to FP, but repetition is the mother of wisdom. We fold, intersect, take, do not inherit. More complex functions are created by the composition of independently testable simpler units. We do not createdivine monoliths, but variable pieces of a kit.

const postService  = <A,B,C>() => ({
  ...withSendMessage,
  ...withCreateMessage,
  ...withReadMessage,
  ...withListItems,
  sayHello: () => 'hello! :-)'
})

And where the hell are the monads?!

From my point of view, I have highlighted three key points for thinking about a more functional style of programming, without deliberately honing directly into the more complex world of higher-kinded types and implementations of structures such as monads.

However, the depths of TypeScript are much more colourful than it might seem at first glance, and in the next articles we will talk about tools that allow such programming at a level often more complex than for languages proclaimed as functional (e.g. Elm without HKT support).

Functional JavaScript does not have to be just lambda, map, reduce and filter. So sometimes next time about FP-TS, higher-kinded types, functorials, monads and type classes, side effects, error handling and asynchronous programming, because that's where the proverbial "Eureka!" Is somewhere.

P.S. The author does not write messages in the refrigerator in Haskell and reserves the right to slightly distort mathematical concepts, etc.