初めに
もともと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
とは
自分の理解で言いますと、いままでのように一回性で必要な引数を入れるのではなく、あらかじめ関数の引数を数段階に分けて、段階別に引数を自由に操作できる考え方です。
例えば参考文章の例のように、
const add = (a, b, c) => {
return a + b + c
}
console.log(add(2, 3, 5)) // 10
これはすべての引数を一回で入れるやり方で、もし最後c
の引数を修正しようとしたら、a
もb
ももう一度引数入れなければなりません。
しかしカリー化した関数は、
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
は、
変数addFirstArgument
とaddSecondArgument
のようにaddCurry(2)
関数の戻り値を入れることによって、Closure
の特性の利用して全部の引数を満たすまで値をキープしている。(引数入れるときの順番に注意)
いろいろ応用してみた
ここから参考文章の例を用いて少し改造し応用してみました。
(ランダムに挨拶するプログラム?)
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
ってやり方ではもっと直感的に分かりやすくなるのでは?
私もそう思いました。でもやっぱり、やっぱり違います...
ちょっと例を見てみましょう。
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