LoginSignup
12
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-01

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

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

Liftsは「LIghtweight Functional programming library for TypeScript」の略です。

インストール方法

yarn add lifts
# or
npm i -S lifts

Do

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

import { Do } from 'lifts'

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

Switch

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

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 |>左辺の値を引数として右辺の関数を呼び出す演算子で、以下のように連ねると一番左の値に対して関数を順に適用することができます。

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です。

import { P } from 'lifts'

const result = P(3, mul2, add1)

配列などを操作する場合はRemedaを使うのがおすすめです。

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

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

PP

PPPasyncバージョンです。

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

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>型を返すようにします。

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

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

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型でエラーを返す例
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を返す例
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型の値で返すための関数です。

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

Liftsの機能は以上です。

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

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

12
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7