Shiopon Labo

しおぽん(@shiopon01)の個人ブログです

Node.jsの初見殺し (x) => (y) => { ... }

引数定義する部分が2つある関数定義、まさに初見殺し。

const a = (x) => (y) => {
 console.log(x * y)
}

ただでさえ function (x) { ... }シンタックスシュガーみたいな (x) => { ... } (アロー関数)があったりするのに、アロー関数には引数定義が2つ付いた (x) => (y) => { ... } みたいな定義方法もある。調べても情報が多くないように思うし、そもそも調べ方もよく分からないかんじがある。この記事はこれの話。( this とかには触れない)

カリー化

さて、結論から言うとこれは カリー化 と呼ばれるもので、先述した関数定義は次の構文のシンタックスシュガーみたいなものである。カリー化についてはいろいろ記事があると思うので 部分適応カリー化 で検索してもらえたらと思う。

const a = function (x) {
  return function (y) {
    console.log(x * y)
  }
}

これは見たとおり、定義した a は引数 x を受け取って新しい関数を生成する関数だ。ここで生成される関数は最初に渡される x を保持しているので、 function (y) の中でも x を利用できる。

実際に 2 を渡して実行してみても、関数が生成されていることが分かる。この関数はもちろん 2 を保持している。

$ node
> const a = (x) => (y) => {
... console.log(x * y)
... }
undefined
> a (2)
[Function]

生成された関数は引数 y を受け取って console.log するだけのものだ。計算に使う x は最初の a (2) で渡したものが引き継がれている。

次の2つは呼び出し方は違うが、結果は同じものである。これは生成された関数に 3 を渡してそのまま実行するパターン。

> a (2)(3)
6

そしてこれが、関数を一旦変数にいれてから 3 を渡して実行するパターン。そのまま実行するパターンではカリー化するメリットが無いので、ふつうはこうやって使う。

> const b = a(2)
[Function]
> b (3)
6

ちなみに関数定義ではいくらでも引数の定義をアローで繋げることができて、つなげた分だけ階層を深くすることができる。好きなだけ繋げるべし。

> const c = (x) => (y) => (z) => {
... console.log (x + y + z)
... }
undefined
> c (1)(2)(3)
6

使い方

この構文を使うタイミングについて少し言述する。

カリー化とはつまり、新たな関数を生み出して処理の共通化を行うためのアプローチだ。関数を生成する際に様々な設定値などを渡すことで、最適な新たな関数を生み出すことができる。この結果、何度も呼び出すが決め打ちの引数が入る煩わしい関数のをスマートに定義することができるようになる。

ありがちだが、例えば次のプログラムは消費税を計算するものだ。ここでは calcJPTax という日本の消費税を計算する新しい関数を生成している。この関数を生成する際に渡す数値を変えて複数定義すれば、国に対応した消費税計算関数を簡単に作成することができる。

const product = (tax) => (value) => {
  return (value * tax)
}

const calcJPTax = product (1.08)

console.log(calcJPTax(100)) //=> 108

少し難しいが、カリー化する際に関数を渡すこともできる。これを使うことで好きな処理を関数に差し込むことができる。

これはコンソールに値を出力する関数で、最初に渡す関数の返り値によって内容を変更できる。

const say = (func) => (value) => {
  return console.log(func(value))
}

const sayHello = say ((name) => {
  return 'Hello, ' + name
})

sayHello('Mario') //=> 'Hello, Mario'

などなど。様々な場面で使いみちがある。