[TypeScript] Liftsでサクッと関数型プログラミング - Pipeline/Switch式/Result型など

TypeScriptで関数型の要素を取り入れてプログラミングしていると、switch文に値を返してほしくなったりPipeline operatorや`Result`型が使いたくなってきます。

fp-tsなどの関数型プログラミング用ライブラリを使うのも一つの手ですが、素のTypeScriptから離れすぎていて学習コストが高そうなので、最低限の機能だけを持った**Lifts**というライブラリを作ってみました。

Liftsは「**LI**ghtweight **F**unctional programming library for **T**ype<b></b>**S**cript」の略です。

https://github.com/yarnaimo/lifts

### インストール方法 

```sh
yarn add lifts
# or
npm i -S lifts
```

## Do

`Do`は**関数の即時実行**を短く書くための関数です。ECMAScriptで策定中の**do式**を元にしています。

```ts
import { Do } from 'lifts'

const result = Do(() => {
    const x = 1
    const y = 2
    return x + y
})
console.log(result) // => 3
```

## Switch

`Switch`はswitch文を**オブジェクトスタイル**で書くための関数です。値を返すこともできます。

```ts
import { Switch } from 'lifts'

const lang = 'en' as string

const result = Switch(lang)(
    {
        ja: () => {
            return '日本語'
        },
        en: () => {
            return '英語'
        },
    },
    () => null, // デフォルト値
)
console.log(result) // => '英語'
```

引数がリテラル型だと**網羅性チェック**もしてくれるので、switch文でありがちなcaseの書き忘れを防ぐことができます。

## Pipe

### P

`P`はECMAScriptで策定中の**Pipeline operator**もどきです。

そもそもPipeline operator **`|>`** は**左辺の値を引数として右辺の関数を呼び出す**演算子で、以下のように連ねると一番左の値に対して関数を順に適用することができます。

```ts
const mul2 = (n: number) => n * 2
const add1 = (n: number) => n + 1

const result = 3 |> mul2 |> add1 // add1(mul2(3)) と同じ; 3 * 2 + 1 = 7
```

Pipeline operatorはまだTypeScriptで使うことができないので、その動作を再現したのが**`P`**です。

```ts
import { P } from 'lifts'

const result = P(3, mul2, add1)
```

配列などを操作する場合は**[Remeda](https://github.com/remeda/remeda)**を使うのがおすすめです。

```ts
import * as R from 'remeda'
import { P } from 'lifts'

const result = P([0, 1, 2], R.map(n => n * 2)) // => [0, 2, 4]
```

### PP

`PP`は`P`の**asyncバージョン**です。

`P`では関数がPromiseを返した場合、**次の関数にそのPromiseがそのまま渡ってしまいます**が、`PP`では**内部で`await`してから次の関数に渡す**ようになっています。

```ts
import { PP } from 'lifts'

const mul2Async = async (n: number) => n * 2
const add1 = (n: number) => n + 1

const result = await P(3, mul2Async, add1) // add1 には Promise<number> が渡るのでコンパイルエラー

const result = await PP(3, mul2Async, add1) // add1 には number が渡るのでOK
```

---

## Result

`Result`はRustやGoのように**エラーを戻り値で返して処理する**ための機能です。

処理が成功した場合は**`Result.ok()`**を使って**`Ok<T>`**型を返し、失敗した場合は**`Result.err()`**で**`Err<E>`**型を返すようにします。

```ts
const fn = (): IResult<T, E> => {
    if (condition) {
        return Result.ok(value)
    } else {
        return Result.err(error)
    }
}
```

型定義はこのようになっています。 (実際には`Ok`と`Err`はクラスです)

```ts
type IResult<T, E> = Ok<T> | Err<E>

type Ok<T> = {
    isOk: true
    value: T
    valueOrError: T
}

type Err<E> = {
    isOk: false
    error: E
    valueOrError: E
}
```

- 日付の文字列をパースして、その日付が有効なら**`Ok`**型で日付を、無効なら**`Err`**型でエラーを返す例

```ts
import { Result } from 'lifts'

const parseDate = (dateStr: string): IResult<Date, Error> => {
    const date = new Date(dateStr)

    // 日付が有効かチェック
    if (!isNaN(date.valueOf())) {
        return Result.ok(date)
    } else {
        return Result.err(new Error('Invalid Date'))
    }
}

console.log(parseDate('2020-04-17'))
// => { isOk: true, value: Date('2020-04-17'), valueOrError: Date('2020-04-17') }

console.log(parseDate('foo'))
// => { isOk: false, error: Error('Invalid Date'), valueOrError: Error('Invalid Date') }
```

- 上の`parseDate`の戻り値が**`Ok`**型ならその日付のDay of monthを返し、**`Err`**型なら`null`を返す例

```ts
const result = parseDate('2020-04-17')

const dayOfMonth = Do(() => {
    if (result.isOk) {
        const { value: date } = result
        return date.getDate()
    } else {
        const { error } = result
        console.error(error)
        return null
    }
})
console.log(dayOfMonth) // => 17
```

### Result.wrap

`Result.wrap`はエラーをthrowするかもしれない関数をラップして、`Ok`型または`Err`型の値で返すための関数です。

```ts
const wrappedResult = Result.wrap(() => {
    if (condition) {
        return value
    } else {
        throw new Error()
    }
})
```

---

Liftsの機能は以上です。

そういえば1年ぐらい前にfp-tsを使ってTrySafeという`Result.wrap`と同じような機能のライブラリを作ってましたがLiftsの方がシンプルで使いやすいと思います。

ぜひ使ってみてください👌