JavaScript プログラミング初学者向け カリーの利用方法

久しぶりにプログラミングの話を書こうと思います。

カリーについてですが、他にも色々詰まっています。

今回はコードを例に挙げて説明します。

まず、以下のような関数があります。

const myFunc = (str) => {
  return str + 'hoge'
}

const getNewArr = (arr, fn) => {
  return arr.map((value) => fn(value))
}

この関数をつかった main 関数があります。

const main = () => {
  const result = getNewArr([1, 2, 3], myFunc)
  console.log(result) // ["1hoge", "2hoge", "3hoge"]
}

myFunc は文字列を受け取り、その文字列に hoge を付けて返します。

getNewArr は新しい配列を返します。第一引数に元になる配列を受け取り、第二引数にその配列の一つ一つを変換して返す関数を受け取ります。

main ではこれらの関数を使い、[1, 2, 3] を [“1hoge", “2hoge", “3hoge"] へ変換しています。

さて、ここで仕様変更が発生しました。

myFunc を汎用的にするために、第二引数を設けたようです。

以下のようになりました。

const myFunc = (str, suffix) => {
  return str + suffix
}

先ほどより柔軟性が増しています。

この変更に合わせて元のコードも変更してみます。どうしましょう。

問題になってくるのは、getNewArr の実装では fn に対して引数を一つしか渡していないことです。

ここを変更してみると以下のようになります。

const myFunc = (str, suffix) => {
  return str + suffix
}

const getNewArr = (arr, fn) => {
  return arr.map((value) => fn(value, 'hoge'))
}

const main = () => {
  const result = getNewArr([1, 2, 3], myFunc)
  console.log(result)
}

main()

これで元のコードと同じ結果になりますが、getNewArr が何やら不穏です。

というのも、この getNewArr の受け取る fn が myFunc の都合で変更されてしまっています。

どういうことかというと、仮に getNewArr が別のところで使われていて、その第二引数の fn が以下のような関数だった場合に、’hoge’は完全に余計です。

const getNewArr = (arr, fn) => {
  return arr.map((value) => fn(value, 'hoge'))
}

const otherFunc = (str) => {
  return str + str
}

const otherMain = () => {
  const result = getNewArr([1, 2, 3], otherFunc)
  console.log(result)
}

otherMain()

otherFunc は引数を一つしか受け取らないのに、getNewArr の実装では 'hoge’ を渡しています。

もともとの getNewArr の役割を文字に起こすと以下のようだったはずです。

  • 第一引数に元になる配列を受け取り、第二引数にその配列の一つ一つを変換して返す関数を受け取ります。そして、これら二つの引数から新しい配列を返します。

しかし、今の実装では謎の 'hoge’ という固定の文字列を fn に渡してしまっていて、役割が良く分からなくなっています。

別のアプローチを考えてみます。

const myFunc = (str, suffix) => {
  return str + suffix
}

const getNewArr = (arr, fn, suffix) => {
  return arr.map((value) => fn(value, suffix))
}

const main = () => {
  const result = getNewArr([1, 2, 3], myFunc, 'hoge')
  console.log(result)
}

main()

今度は getNewArr に第三引数を追加しました。

これによって、getNewArr から固定の 'hoge’ は消えました。

しかし、先ほどと同じような問題が起きています。

というのも、getNewArr の第二引数に受け取る fn は必ず二つの引数を受け得とらないといけない、という制約が発生しています。

しかも、fn(value, suffix) という記述から、第二引数は末尾語を表す値であり、myFunc のような関数限定になりそうです。

このままでは汎用的だった getNewArr がどんどん myFunc の都合で変化していってしまいそうです。

ここでカリー化を使います。

先に言うと、関数の引数を一つにすることで、その関数を使う時の汎用性があがります。

以下のようにします。

const myFunc = (suffix) => (str) => {
  return str + suffix
}

const hogeFunc = myFunc('hoge')

const getNewArr = (arr, fn) => {
  return arr.map((value) => fn(value))
}

const main = () => {
  const result = getNewArr([1, 2, 3], hogeFunc)
  console.log(result)
}

main()

myFunc をカリー化し、そこからhogeFuncを作成します。

hogeFunc は引数に str 一つを受け取るという関数です。

そのため、それ以外のコードは当初と一切変更がありません。

繰り返しになりますが、カリー化を使うポイントしては、関数の引数を一つに減らしたいときに有効だと思っています。

どういうときに関数の引数を一つに減らしたいかというと、今回の getNewArr のように、その関数が他の関数の引数と渡される、というケースです。

getNewArr の第二引数の fn は、もともと引数が一つの関数を想定していました。

そのため、myFuncの引数を一つにするためにカリー化をした、ということになります。

他にも、関数型プログラミングでチェーンのようなことをしたいときにも、関数の引数を一つに減らしたいことがあると思います。そういった時のも強力です。

逆に引数を一つに減らしたいという要求がなければ、無理にカリー化しなくて良いと思っています。