4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaScriptのカリー化について

Last updated at Posted at 2022-07-01

初めに

もともとthisのこと調べる途中で現れた、
ある書き方です。

const addCurry =(a) => {
    return (b)=>{
        return (c)=>{
            return a+b+c
        }
    }
}
console.log(addCurry(2)(3)(5)) // 10

えっ何で引数のあとにまた別の引数が?
addCurry(2, 3, 5)ではなくaddCurry(2)(3)(5)で動けるの?
(早速真似して動かせてみた)動け..た!?
何この書き方、はじめて見た!(新大陸発見した気分です)

そして何日かいろんな参考文章をかみ砕いて、Curryingが利用しているClosureがまだはっきり分かっていないので一緒にまとめてみました。
こちらです↓
JavaScriptのクロージャについて

そして今回はカリー化の理解をまとめていきたいと思います!
参考文章はこちらです↓
Understanding JavaScript currying
Closures and currying

JavaScriptのCurryingとは

自分の理解で言いますと、いままでのように一回性で必要な引数を入れるのではなく、あらかじめ関数の引数を数段階に分けて、段階別に引数を自由に操作できる考え方です。
例えば参考文章の例のように、

Noncurried version
const add = (a, b, c) => {
  return a + b + c
}
console.log(add(2, 3, 5)) // 10

これはすべての引数を一回で入れるやり方で、もし最後cの引数を修正しようとしたら、abももう一度引数入れなければなりません。
しかしカリー化した関数は、

Curried version
const addCurry = (a) => {
  return (b) => {
    return (c) => {
      return a + b + c
    }
  }
}
console.log(addCurry(2)(3)(5)) // 10

えっ何か書き方だけ変わったけど結果は変わってないんじゃない?
お気持ち痛いほどわかります!しかしCurryingが理解できたら見方全然変わってきますよ。
説明する前にちょっとほかの例を見ていきたいと思います。
まずは上の例をこう書いたらどうなるでしょう?

const addCurry = (a) => {
  return (b) => {
    return (c) => {
      return a + b + c
    }
  }
}
// console.log(addCurry(2)(3)(5)) // 10

const addFirstArgument = addCurry(2)
console.log(addFirstArgument(3)(5)) // 10
const addSecondArgument = addFirstArgument(3)
console.log(addSecondArgument(5)) // 10

相変わらず引数が三つが必要だけど、変数に入れることによって毎回入れる引数が減っていくんです。

簡単にいうとCurryingは、
変数addFirstArgumentaddSecondArgumentのようにaddCurry(2)関数の戻り値を入れることによって、Closureの特性の利用して全部の引数を満たすまで値をキープしている。(引数入れるときの順番に注意)

いろいろ応用してみた

ここから参考文章の例を用いて少し改造し応用してみました。
(ランダムに挨拶するプログラム?)

Basic curring example
const obj = {
  greeting: ['Hi', 'Hello', 'Hey'],
  guest: ['Mary', 'Mick', 'Lucy', 'Daisy'],
  message: 'Welcome to the internet!'
}

const greetings = obj.greeting
const guests = obj.guest

function radom(range) {
  return Math.floor(Math.random() * range)
}

// function Welcome(greet) {
//   return function (name) {
//     return function (message) {
//       return `${greet} ${name}, ${message}`
//     }
//   }
// }

// Modern currying with ES6
const Welcome = greet => name => message =>
  `${greet} ${name} ${message}`

// console.log(Welcome(greetings[radom(greetings.length)])(guests[radom(guests.length)])(obj.message))

const sendGreet = Welcome(greetings[radom(greetings.length)])
const sendName = sendGreet(guests[radom(guests.length)])
const sendMessage = sendName(obj.message)
console.log(sendMessage)

ここは必要なデータが全部objから抽出したけど、ほかのDBやAPIを使ったり好きな段階(引数の順番)にデータを埋め込みしたり、さらに自由度が上がると思います。

Currying vs. Partial application

わざわざCurryingを書かなくても、Partial applicationってやり方ではもっと直感的に分かりやすくなるのでは?
私もそう思いました。でもやっぱり、やっぱり違います...
ちょっと例を見てみましょう。

Currying vs. partial application
const addPartial = (x, y, z) => {
  console.log(x, y, z)
  return x + y + z
}

const partialFunc = addPartial.bind(this, 2, 3)
console.log(partialFunc())
// 2 3 undefined
// NaN
console.log(partialFunc(5))
// 2 3 5
// 10

簡単に言うと、Partial applicationでは、絶対ではないけどよくbind()などのバインドメソッドを使って強制的にthisのコンテキストを変えて、新しい関数を返すことになる。(function borrowing

Curryingが求める一回に一つの引数のやり方と違って、Partial applicationはコンテキストを変えることで、別の方向にできることが増えました。

必ずしもどれを使ったほうがいいのではなく、使い分けが大事だと思います。

Curryingおまけの検証コード

const add = function (x) {
  console.log(x)
  return function (y) {
    console.log(x, y)
    return x + y
  }
}

let increment = add(1)
let addTen = add(10)

console.log(increment(2)) // 3
console.log(addTen(2)) // 12

// 1 // increment = add(1), log x
// 10 // addTen = add(10), log x

// 1 2 // increment(2), log x, y
// 3 // increment(2), log x + y

// 10 2 // addTen(2), log x, y
// 12 // addTen(2), log x + y

// note: 'add' accepts one params and return function
// note: return value will save in the closure and keep 'add's first params

// compare
console.log(add(1)(2)) // 3
console.log(add(10)(2)) // 12

// 1 // add(1)(2), log x
// 1 2 // add(1)(2), log x, y
// 3 // add(1)(2), log x + y

// 10 // add(10)(2), log x
// 10 2 // add(10)(2), log x, y
// 12 // add(10)(2), log x + y
4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?