Level up your Typescript game, functionally - Part 2
Tags: typescript, functional, programming, scala
Published: 2023-11-25 01:11:03

Welcome back to another post in the series "Level up your Typescript game". This is the second post in the series.

In the earlier post, we looked at some Typescript trends, some challenges in handling complex apps and a better pattern to model errors to avoid the try/catch hellscape. In this post, let's look at some new concepts and take some learnings from other languages and apply it to Typescript.

There are a few different types of programming languages: 
([Source 1](https://www.coursera.org/articles/types-programming-language) | [Source 2](https://en.wikipedia.org/wiki/Functional_programming))

1. **Procedural** - A procedural language follows a sequence of statements or commands in order to achieve a desired output. Examples: _C, C++, Java, Pascal, BASIC etc._

2. **Object Oriented** - An Object Oriented language treats a program as a group of objects composed of data and program elements, known as attributes and methods. Objects can be reused within a program or in other programs. Examples: _Java, C++, PHP, Ruby etc._

3. **Functional** -  Functional programming languages are constructed by applying and composing functions. It is a declarative programming paradigm in which function definitions are trees of expressions that map values to other values. Examples: _Scala, F#, Erlang, Haskell, Elixir etc._

4. **Scripting** - Scripting languages are used to automate repetitive tasks, manage dynamic web content, or support processes in larger application. Examples: _Javascript, Node.JS, Python, Bash, Perl etc._

5. **Logical** - A logic programming language expresses a series of facts and rules to instruct the computer on how to make decisions. Examples: _Prolog, Absys, Datalog, Alma-0_

All these different types of languages have their own pros and cons, design trade offs and respective use cases.

Typescript and Javascript are scripting languages by nature and we only have access to features that are part of the ECMAScript standard; which is evolving constantly and new features are added all the time. However can we take a few concepts and advanced features from other languages and apply them to Typescript now? Let's explore below.

For the remainder of this post, I want to focus on functional programming concepts because those ideas seem to have the most benefit for building modern complex applications. We'll consider languages like Scala and Rust and look at how they do things to achieve maximum productivity and effectiveness. 

In many functional programming languages like `Scala` and `Haskell`, there is a concept called `Monad`. For the rest of this post we'll focus on features of the `Scala` programming language in particular. 

> Monads are nothing more than a mechanism to sequence computations around values augmented with some additional feature. Such features are called effects. A monad adds an effect to a value wrapping it around a context. In Scala, one way to implement a monad is to use a parametric class on the type.

```scala
class Lazy[+A](value: => A) {
  private lazy val internal: A = value
}
```

> The Lazy type wraps a value of type A, provided by a “call-by-name” parameter to the class constructor to avoid eager evaluation.

But how do we sequence computations over a value wrapped inside a monad? 

Since it is cumbersome to extract the monad’s wrapped value to apply operations, we need another function that can handle this for us. In Scala this is called the `flatMap` function. A `flatMap` function takes as input another function from the value of the type wrapped by the monad to the same monad applied to another type. A monad must provide a flatMap function to be considered one. 

```scala
def flatMap[B](f: (=> A) => Lazy[B]): Lazy[B] = f(internal)
// Using our earlier example
val lazyString42: Lazy[String] = lazyInt.flatMap { intValue =>
  Lazy(intValue.toString)
}
```

> We changed the value inside the monad without extracting it. So, any chain of flatMap invocation lets us make any sequence of transformations to the wrapped value.

Monads by nature also must also follow three laws

1. **Left identity**

> Applying a function f using the flatMap function to a value x lifted by the unit function is equivalent to applying the function f directly to the value x.

```scala
Monad.unit(x).flatMap(f) = f(x)
// With the earlier example
Lazy(x).flatMap(f) == f(x)
```

2. **Right identity**

> Application of the flatMap function using the unit function as the function f results in the original monadic value

```scala
x.flatMap(y => Monad.unit(y)) = x
// With the earlier example
Lazy(x).flatMap(y => Lazy(y)) == Lazy(x)
```

3. **Associativity**

> Applying two functions f and g to a monad value using a sequence of flatMap calls is equivalent to applying g to the result of the application of the flatMap function using f as the parameter.

```scala
x.flatMap(f).flatMap(g) = o.flatMap(x => f(x).flapMap(g))
// With the earlier example
Lazy(x).flatMap(f).flatMap(g) == f(x).flatMap(g)
```

> If a monad satisfies the three laws, then we guarantee that any sequence of applications of the unit and the flatMap functions leads to a valid monad — in other words, the monad’s effect to a value still holds. Monads and their laws define a design pattern from a programming perspective, a truly reusable code resolving a generic problem.

[ [Scala Monads By baeldung](https://www.baeldung.com/scala/monads) ]

Now that we've understood Monads, let's take a look at some example Monads in Scala. 

1. `Option` - An option type represents an optional value aka a value that is either defined or not. This provides type safety and also ensures that if the optional value is used anywhere in the code; it is functionally aware using it's monad properties.

```scala
val maybeInt: Option[Int] = Some(42) // Value defined
val maybeInt2: Option[Int] = None // No value defined
```

2. `Either` - An Either type models a disjoint union that represents a value that is exactly one of two types.

```scala
val eitherValue: Either[String, Int] = Right(42) // Right type
val eitherValue2: Either[String, Int] = Left("42") // Left type
```

It is most commonly used to model Errors as opposed to throwing exceptions

```scala
val eitherSuccessOrError: Either[Error, Int] = Right(42)
val eitherSuccessOrError: Either[Error, Int] = Left(Error("no 42"))

// instead of 
if (someCondition) 42 else throw new Error("no 42") else 
```

Note: This type of composition for modeling errors of operations very similar to what we saw in our previous post with the tuple type.

Now let's try to implement these monads in Typescript so we can use these functional concepts. 

### The `Option` type

```typescript
// We wrap the value inside an object
type Some<T> = { value: T }

// We want something better than null or undefined 
type None = Record<string, never>

// The Option type
type Option<T> = Some<T> | None
```

It's going to get cumbersome to keep wrapping our values to represent `Some<T>` and `None` so let's create some helper methods.

```typescript
// Wrapper to create a Some<T> value
const some = <T>(value: T): Some<T> => ({ value })
// Wrapper to create a None value
const none = (): None => ({})
```

Now let's use our new types!

```typescript
const someValue: Option<number> = some(1) // { value: 1 }
const noValue: Option<number> = none() // {}
```

That's really cool! Now how do we use this in a functional way? 

If I want to know whether a value is defined (Some(value)) or not (None), I have to inspect my `Option` type

```typescript
const isDefined = <T>(option: Option<T>) => !!option?.value
```

This works but it's not really scaleable enough to use everywhere.

What would be cool is if we can pattern match on the type. This is implemented in a lot of functional languages. For example in Scala, this is how we can match on the `Option` type.

```scala
val option: Option[Int] = Some(42)

option match {
 case Some(value) => println(s"Value is defined: $value")
 case None => println("Value is not defined")
}
```

Pattern matching is great! It allows you to functionally determine intent from the type and perform operations based on the tree. 

Unfortunately Javascript doesn't support pattern matching...yet!

There's an [ECMAScript proposal](https://github.com/tc39/proposal-pattern-matching) that is in the works to add this feature to the language! It's going to look something like this.

```javascript
match (res) {
  when ({ status: 200, body, ...rest }): handleData(body, rest)
  when ({ status, destination: url }) if (300 <= status && status < 400):
    handleRedirect(url)
  when ({ status: 500 }) if (!this.hasRetried): do {
    retry(req);
    this.hasRetried = true;
  }
  default: throwSomething();
}
```

Well it's still a work in progress and might take some time to be added to the language. In the meantime however, let's build our own pattern matching mechanism.

To mirror the case match, let's create an interface to support the tree paths.

```typescript
// Option Matchers 
interface Matchers<T, R1, R2> {
    none(): R1
    some(value: T): R2
}
```

The `Matchers` interface takes in a type `T` for the exists path of the `Option`, a type `R1` for the response type of the `none()` path and a type `R2` for the response type of the `some(v)` path.

Next, let's build a function to actually do the matching

```typescript 
// Option match function
const match =
    <T, R1, R2>(matchers: Matchers<T, R1, R2>) =>
    (option: Option<T>) =>
        'value' in option ? matchers.some(option.value) : matchers.none()
```

The above match function takes in the matchers interface object and applies it to the actual `Option<T>` value. Internally it checks the signature of the `Some<T>` type and if it exists, it calls the `matchers.some(..)` fn and if not the `matchers.none()` fn to satisfy both tree paths.

Now let's see this in action

```typescript
const someValue: Option<number> = some(42) // { value: 42 }
const noValue: Option<number> = none() // {}

const result = match({
   some: (value) => value
   none: () => undefined
})(someValue) // 42

const result2 = match({
   some: (value) => value
   none: () => 'default'
})(noValue) // 'default'
```

This seems to work really nicely! Now let's compose this in a pipeline flow. Due to the `match` functions functional nature, we can apply it easily. A pipeline pipes the result of each operation as an input to the next operation in a sequence.

```
op1 => op2(op1_result) => op3(op2_result) => ...
```

To create our pipeline, I'm going to use the `pipe` function from the NodeJS [ramda](https://github.com/ramda/ramda) library instead of building my own.

```typescript
const optionPipeline = pipe(
    () => some(42),
    match({
        some: (value) => value + 1,
        none: () => undefined,
    }),
    (input) => input.toString()
) // { value: 42 } => 42 + 1 => 42.toString() => '42'
```

Notice how this flows really nicely! We created an `Option<Int>`, passed it through our custom match function which added 1 to our Option if it exists and then converted the result to a string.

Imagine using this to build complex pipelines in your apps; with the functional type safety you don't have to worry about those pesky "what if this is undefined?" checks!

Next let's look at the `Either` type and define that in Typescript

### The `Either` Type

```typescript 
type Right<T> = { value: T }
// We are using the Left type to model errors
type Left<E = Error> = { error: E }

// The Either Type
type Either<E = Error, T> = Right<T> | Left<E>
```

With the new `Either<E, T>` type, we are primarily modeling a union that can be either a `Left` type or Error and a `Right` type that can be a success value type. 

Just like we did with the `Option` type, let's create some utilities and match functions to make the type practical. 

Wrappers for the Either type can be done this way

```typescript
const right = <T>(value: T): Right<T> => ({ value })
const left = <E>(error: E): Left<E> => ({ error })
```

Next let's create our matchers interface

```typescript
interface Matchers<E extends Error, T, R1, R2> {
    left(error: E): R1
    right(value: T): R2
}
```

Similar to the `Option` case, the matchers for the `Either` type now takes in an extra argument for the Error type since Either consists of 2 generic types (left error type and right value) as opposed to the Option that only had 1 generic Type (the value)

And our match function now looks like this

```typescript 
const match =
    <E extends Error, T, R1, R2>(matchers: Matchers<E, T, R1, R2>) =>
    (either: Either<E, T>) =>
        'value' in either
            ? matchers.right(either.value)
            : matchers.left(either.error)
```
We are using a similar implementation for the match function as we did in the `Option` case by inspecting the either object and looking for a property (`value`) that is in the `Right` case (`{ value }`) but not in the left case (`{ error }`). 

Note that this is just one implementation but you can also have your left and right types take any shape or form and update the match function to match your signatures. Another common option to do this is to use a discriminator type as follows.

```typescript 
type Right<T> = { typename: 'right', value: T }
type Left<E> = { typename: 'left', error: E }
```
And your match function can inspect the `typename` property instead.

But circling back, let's use our new types in a practical pipeline flow

```typescript
const right: Either<Error, number> = right(42) // { value: 42 }
const left: Either<Error, number> = left(new Error('error')) // { error: Error('error') }


const eitherPipeline = pipe(
    () => right,
    match({
        right: (value) => value + 1,
        left: () => undefined,
    }),
    (input) => input.toString()
) // { value: 42 } => 42 + 1 => 42.toString() => '42'

```

And there you have it, we implemented a functional type for `Either` that is practical and can scale. 

Here's another functional use case. Let's redo our tuple implementation from Part 1 using the `Either` type instead of the Tuple type.

```typescript
const getDataFromDB = 
  async (ctx: Context): Promise<Either<Error, Data>> => {
    try {
      const result = await ctx.db.primary.getOne()
      return right(result)
    } catch (err) {
      console.error('Error getting data from DB', err)
      return left(err)
    }
  }

const app = async () => {
   const eitherData = await getDataFromDB(ctx)
   match({
     left: (err) => throw err,
     right: (value) => value
   })(eitherData)
}
```

In my opinion, this looks like a very pragmatic way to compose results of operations. 

In the next and final post, let's look at an alternative type to the `Either` type that takes inspiration from the `Rust` programming language. We will also summarize what we have learned along with a handy library that encompasses all these concepts.

_Bonus: If you like the idea of pattern matching, also check out [this library](https://github.com/gvergnaud/ts-pattern) that can help you with pattern matching use cases until the ECMAScript pattern matching standard is added to Javascript._

Congrats on making it to the end of this post! You have leveled up 🍄🍄

If you found this post helpful, please upvote/react to it and share your thoughts and feedback in the comments.

Onwards and upwards 🚀