Who can see the elegance of functional programming and not fall in love with it?

For me, functional programming is an unattainable dream. It’s beautiful, but a little detached from reality. If it’s crazy to start using functional programming languages like Haskell entirely, it makes a lot of sense to learn useful ideas from the accumulated wisdom of this programming paradigm and apply them to everyday development. Fp-ts gave us that opportunity. While it’s far less elegant and perfect to use than Haskell directly, it’s a good compromise between functional and imperative programming.

Many of the concepts in FP-TS, such as typeclass, instance, and so on, are derived from Haskell, which may not be very friendly to someone who has never used Haskell before. Therefore, this article will introduce some modules provided by FP-TS according to my own experience and personal understanding. I hope it will be helpful for those who are interested in FP-TS. Of course, due to my limited experience and ability, there are inevitably mistakes and omissions. Hope we can get your correction, also welcome to exchange and discussion.

Preliminary knowledge

There are a few things you need to know before you start.

type

In the first section of C we will learn a lot of types, such as int, char, etc. Although JavaScript is weakly typed and we do not need to specify the type of a variable when declaring it, JavaScript values are also typed. For example, “ABC” is of type string and 1 is of type number. TypeScript adds a static type system to the language. In TypeScript, we use type annotations to describe the type of a variable or parameter. It also gives us the ability to describe more complex types through things like union types.

Values belonging to the same type can form a set that contains all possible values of that type. So we can use “is a member of” to describe the relationship between a value and a type. For example true is a member of type Boolean.

type constructor

As the name implies, type constructor is used to create types. The familiar Array is a type constructor. Array itself cannot be used directly as a value type; it must accept another type:

const x: Array = []; // Error Generic type 'Array
      
       ' requires 1 type argument
      

const y: Array<string> = []; / / right
Copy the code

In TypeScript, Type constructor is emulated with generic Type

Array is also a type, but it is very different from the number and string types. So we must introduce a concept to distinguish between these types. Just as a value has a type, a type itself has a type, the type of a type, called kind. The types that can be used as value types, such as number, are called concrete types. Their kind can be denoted by *. Array’s kind is * -> *, which means you must provide another concrete type to get a concrete type that can be used as a value. Such a type is called a higher-kinded type (later abbreviated HKT). Of course, there are more types of HKTS that need to be accepted, such as HKTS with kind of * -> * -> * that need to accept two parameters. * -> * -> * this notation also implies that when we provide only one type, we will get a KIND HKT of * -> *. However, this cannot be simulated using TypeScript’s Generic Type, which requires that we also provide the required type parameters.

typeclass

Observe Haskell’s definition of the function show :: show a => a -> String, => This is used to express constraints on type variables. The meaning of Show A is that type A must satisfy Show Typeclass. We can imagine typeclass as a community, such as Eq club, Show club, and various types of members. If you want to be a member of a club, you must meet the club’s entry requirements. For example, Eq requires that the checked type support equal operations, that is, an equals function must be implemented, which takes any two values a and B belonging to type and outputs a Boolean. In Haskell we can use the instance declaration and provide an implementation of the member defined by typeclass to provide proof of membership for a type. In FP-TS, typeclass is emulated using an interface.

In this sense, typeclass functions like an interface to describe types that have certain behavior characteristics or support certain operations.

Eq

Now that we’ve mentioned Eq as a typeclass, let’s look at what Eq can do for us and how it can be used. Eq represents the type that can be equalized. It is defined in FP-TS as follows:

interface Eq<A> {
  readonly equals: (x: A, y: A) = > boolean;
}
Copy the code

Suppose we have a type Point defined as follows:

interface Point {
    x: number;
    y: number;
}
Copy the code

We assume that the points where x and y are both equal are equal, so we write an implementation of equals as follows:

const equals = (a: Point, b: Point) = > a.x === b.x && a.y === b.y;
Copy the code

We define an instance of Eq for Point:

const eqPoint: Eq<Point> = {
  equals
}
Copy the code

So what does Eq do for us? Look at the definition of an ELEm function provided by the Array module of FP-TS:

declare const elem: <A>(
  E: Eq<A>
) = > {
  (a: A): (as: Array<A>) = > boolean
  (a: A, as: Array<A>): boolean
}
Copy the code

This function requires an argument of type Eq, that is, an instance of Eq must be defined for type A. And our Point satisfies that condition. We can then use the elem function to determine whether a Point exists in an Array of type Array :

const points: Array<Point> = [
  { x: 1.y: 2 },
  { x: 2.y: 2 },
  { x: 3.y: 4},]; elem(eqPoint)({x: 1.y: 2 })(points) // true
elem(eqPoint)({ x: 3.y: 2 })(points) // false
Copy the code

Fp-ts already provides instance of Eq for types such as number, string, and so on. When creating our own instance, we can also choose to use these existing instances:

import { Eq as eqNumber } from "fp-ts/number";
import { Eq, struct } from "fp-ts/Eq";

const eqPoint: Eq<Point> = struct({
  x: eqNumber,
  y: eqNumber
})
Copy the code

Ord

Ord stands for the type that supports comparison operations, so a new requirement is added to Eq:

type Ordering = -1 | 0 | 1

interface Ord<A> extends Eq<A> {
  readonly compare: (first: A, second: A) = > Ordering
}
Copy the code

In order to create Ord instance, we must create Eq instance first. And what does the ability to compare do for us? Fp-ts /Array provides a function for sorting:

const sort: <B>(O: Ord<B>) = > <A extends B>(as: A[]) => A[]
Copy the code

Suppose we have a type definition as follows:

interface User {
    name: string;
    age: number;
}
Copy the code

We want to sort users by age. To use sort, we need to provide an Ord instance for the User type:

const equals = (a: User, b: User) = > a.age === b.age
const compare = (a: User, b: User) = > equals(a, b) ? 0 : a.age > b.age ? 1 : -1

const ordUser: Ord<User> = {
  equals: equals,
  compare: compare 
}

const users = [
  {
    name: "Tomas".age: 25
  },
  {
    name: "Ammy".age: 21
  },
  {
    name: "Kat".age: 23
  }
]

sort(ordUser)(users) // [{"name":"Ammy","age":21},{"name":"Kat","age":23},{"name":"Tomas","age":25}]
Copy the code

Similarly, FP-TS provides Ord instance for number type. We can also use an existing instance to simplify the process of creating an instance for User:

const ordUser = contramap<number, User>(u= > u.age)(eqNumber)
Copy the code

From these two examples, we have seen the role and use of typeclass and instance. In the following articles, we formally begin to look at some of the types offered by FP-TS.