Blog
fp-ts library in Hoppscotch codebase.

fp-ts library in Hoppscotch codebase.

In this article, we review fp-ts library in Hoppscotch codebase. We willl look at:

  1. What is Hoppscotch?

  2. What is fp-ts?

  3. fp-ts usage in Hoppscotch CLI.

I study patterns used in an open source project found on Github Trending. For this week, I reviewed some parts of Hoppscotch codebase and wrote this article.

What is Hoppscotch?

Hoppscotch is an open source API development ecosystem — https://hoppscotch.io. It is an alternative to Postman, Insomnia.

Check out the Hoppscotch documentation to learn more.

What is fp-ts?

fp-ts provides developers with popular patterns and reliable abstractions from typed functional languages in TypeScript.

The goal of fp-ts is to empower developers to write pure FP apps and libraries built atop higher order abstractions. It includes the most popular data types, type classes, and abstractions from languages like Haskell, PureScript, and Scala.

Learn more about fp-ts library.

fp-ts usage in Hoppscotch CLI.

I came across the following import in packages/hoppscotch-cli/src/index.ts

import * as E from "fp-ts/Either";

Then, I read about Either in the fp-ts documentation

Either

type Either<E, A> = Left<E> | Right<A>

Represents a value of one of two possible types (a disjoint union).

An instance of Either is either an instance of Left or Right.

A common use of Either is as an alternative to Option for dealing with possible missing values. In this usage, None is replaced with a Left which can contain useful information. Right takes the place of Some. Convention dictates that Left is used for failure and Right is used for success.

Below is an example copied from the documentation.

import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'

const double = (n: number): number => n * 2

export const imperative = (as: ReadonlyArray<number>): string => {
  const head = (as: ReadonlyArray<number>): number => {
    if (as.length === 0) {
      throw new Error('empty array')
    }
    return as[0]
  }
  const inverse = (n: number): number => {
    if (n === 0) {
      throw new Error('cannot divide by zero')
    }
    return 1 / n
  }
  try {
    return `Result is ${inverse(double(head(as)))}`
  } catch (err: any) {
    return `Error is ${err.message}`
  }
}

export const functional = (as: ReadonlyArray<number>): string => {
  const head = <A>(as: ReadonlyArray<A>): E.Either<string, A> =>
    as.length === 0 ? E.left('empty array') : E.right(as[0])
  const inverse = (n: number): E.Either<string, number> => (n === 0 ? E.left('cannot divide by zero') : E.right(1 / n))
  return pipe(
    as,
    head,
    E.map(double),
    E.flatMap(inverse),
    E.match(
      (err) => `Error is ${err}`, // onLeft handler
      (head) => `Result is ${head}` // onRight handler
    )
  )
}

assert.deepStrictEqual(imperative([1, 2, 3]), functional([1, 2, 3]))
assert.deepStrictEqual(imperative([]), functional([]))
assert.deepStrictEqual(imperative([0]), functional([0]))

However, I did not see any reference to left or right as demonstrated above but instead I saw .toError being called as shown below:

program.exitOverride().configureOutput({
  writeErr: (str) => program.help(),
  outputError: (str, write) =>
    handleError({ code: "INVALID_ARGUMENT", data: E.toError(str) }),
});

Learn more about toError method.

About me:

Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.

Email: ramu.narasinga@gmail.com

I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com

References:

  1. https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-cli/src/index.ts

  2. https://gcanti.github.io/fp-ts/modules/Either.ts.html

  3. https://gcanti.github.io/fp-ts/