Help us understand the problem. What is going on with this article?

TypeScriptで関数型っぽくプログラミングする

@maxfie1dと申します。Webが得意なフロントエンドエンジニアです。
Qiitaの記事は久しぶりなので少し緊張気味です。

この記事はTypeScript(TS)で関数型っぽくプログラミングしてみようという記事です。なぜ関数型っぽく書くかというと、明確で分かりやすく簡単なコードになるからです。いやマジで!

この記事で言う関数型っぽいとはどういうことかというと...

  • Maybe<T>, Result<E, T>
  • カリー化
  • イミュータブル
  • パイプ、合成関数
  • パターンマッチ

これらを実現できたら(僕的には)充分です。順番にやってみましょう!

※詳しい用語や背景の説明は飛ばすので、関数型言語を触ったことの無い人にはつらいかもしれません。ですが、サンプルコードを見れば大体の動きは想像できると思います。とにかく、こういう書き方できるんやなーと知ってくれたら嬉しいです!ライブラリにFolktaleRamdaを使用します。

Maybe<T>, Result<E, T>

TSだと一般的にundefinednull、もしくは例外で「値が存在しないこと」や「失敗」を表しますが、Maybe<T>Result<E, T>を使うという方法もあります。

Maybeの例
import { maybe, Maybe } from "folktale";

const myMaybe: Maybe<number> = trueOrFalse()
  ? maybe.Just(123)
  : maybe.Nothing();

myMaybe.matchWith({
  Just: ({ value }) => {
    console.log(value); // => 123
  },
  Nothing: () => {
    console.log("値がないよ");
  }
});
Resultの例
import { result, Result } from "folktale";

const myResult: Result<string, number> = trueOrFalse()
  ? result.Ok(123)
  : result.Error("失敗だよ");

myResult.matchWith({
  Ok: ({ value }) => {
    console.log(value); // => 123
  },
  Error: ({ value }) => {
    console.log(value); // => "失敗だよ"
  }
});

Maybe<T>Result<E, T>の違いは、異常の場合に値を持つかどうかです。

イミュータブル

TSだと、配列の要素追加をarray.push(...)で行いますが、これは要素を追加した新しい配列を返すのではなく既存の配列に値が追加されます。つまりミュータブルです。(ちなみにArray.pushの返り値の型はnumberで新しい配列の長さが返されます。)

イミュータブルにやってみましょう。

イミュータブルな配列操作の例
import * as R from "ramda";

const array = [1, 2, 3];
// ミュータブル
array.push(4);

console.log(array);  // => [1, 2, 3, 4]

// イミュータブル
const array5 = R.append(5, array);
const array0 = R.prepend(0, array);

console.log(array);  // => [1, 2, 3, 4]
console.log(array5); // => [1, 2, 3, 4, 5]
console.log(array0); // => [0, 1, 2, 3, 4]

R.appendR.prependも元の配列arrayに影響を与えません。つまりイミュータブルです。やったね!

カリー化

朗報!
カリー化されていない関数を後からカリー化することができます。

カリー化の例
import * as R from "ramda";

const add3 = (a: number, b: number, c: number) => a + b + c;
const curriedAdd3 = R.curry(add3);

console.log(add3(1, 2, 3));        // => 6

console.log(curriedAdd3(1)(2, 3)); // => 6
console.log(curriedAdd3(1, 2)(3)); // => 6
console.log(curriedAdd3(1)(2)(3)); // => 6

パイプ、合成関数

僕はF#で関数型言語を学んだのですが、F#にはPipeline Operator |>というものがあります。パイプライン演算子を使うと、通常関数(引数)の順で記述するところを逆の引数 |> 関数の順で記述することができます。

他にもF#にはComposition Operator >>というものがあり、f >> gのようにして合成関数を作ることができます。

残念ながらTSでは|>>>といった演算子は使用できませんが、似たことをRamdaを使ってやってみましょう。pipecomposeの違いは関数の適用順序です。

pipeとcomposeの例
import * as R from "ramda";

type Fn = (xs: number[]) => number[];
const f: Fn = R.filter(x => x % 2 === 0);
const g: Fn = R.map(x => x * 2);
const h: Fn = R.filter(x => x > 5);
const numbers = R.range(1, 10);

const r1 = R.pipe(f, g, h)(numbers);
const r2 = R.compose(f, g, h)(numbers);

console.log(r1); // => [8, 12, 16]
console.log(r2); // => [12, 14, 16, 18]

関数をパイプしたり合成するメリットの1つは不要な一時変数(しばしば命名にも悩む)を置く必要がなくなることです。

ちなみに、2019年11月26日現在パイプライン演算子はECMAScriptでstage-1なんですね。知らなった。try-babelで試せます(presetsstage-1をオンにしておきましょう)。

将来的にTSでもパイプライン演算子が使えるようになったら嬉しいですね。

(ちなみにちなみに、記事を書いている途中にRubyの開発版でパイプライン演算子が導入されたことを知りました。まじか。)

パターンマッチ

関数型っぽく書きたいならパターンマッチも欲しいですよね。しかし、残念ながら現状では難しいみたいです。提案はstage-1で、将来的には使えるようになるかもしれませんがいつのことになるやら。

どうしてもやってみたい人はbabelの実装funcyなどを使ってみましょう。

おしまいに

FolktaleRamdaを使うことで、TSでも結構関数型っぽく書くことができて関数型スタイルの恩恵を受けることができます。

他にもJSのプラットフォームで関数型を使うアイデアとして、例えばF#をJSにコンパイルするFable、JSコンパイルに対応しているKotlin、AltJSのLiveScriptPureScriptReasonを使うという手があります。

awesome-fp-jsを覗いてみるともっと面白い発見があるかもしれません!

リソース

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした